Skip to content

Commit e8b1fb7

Browse files
committed
Add Pulse Waveform Generator (PWG) for DMA-driven GPIO pulse trains
Timer-triggered DMA writes to GPIO BSRR from a circular double buffer, producing jitter-free pin toggling at configurable frequency. A background thread refills the inactive buffer half via user-supplied callback. Intended for stepper motor control and similar precise pulse applications. STM32H7 support with DMAMUX routing for TIM1-TIM8.
1 parent 4cae57a commit e8b1fb7

6 files changed

Lines changed: 257 additions & 0 deletions

File tree

include/mios/pwg.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* @file pwg.h
3+
* @brief Pulse Waveform Generator (PWG)
4+
*
5+
* The PWG generates jitter-free pulse trains by using a hardware timer
6+
* to trigger DMA transfers from a memory buffer to a GPIO port's BSRR
7+
* (Bit Set/Reset Register). This produces cycle-accurate pin toggling
8+
* with zero CPU involvement during steady-state operation.
9+
*
10+
* A background thread refills the inactive half of a circular
11+
* double-buffer via a user-supplied callback. At the configured
12+
* timer frequency, DMA writes one 32-bit word per tick to BSRR:
13+
*
14+
* Bits 0-15: Set corresponding GPIO pin high
15+
* Bits 16-31: Reset corresponding GPIO pin low (bit 16 = pin 0, etc.)
16+
*
17+
* Writing 0 to a BSRR word leaves all pins unchanged for that tick.
18+
*
19+
* All controlled pins must reside on the same GPIO port. For pins
20+
* on different ports, create separate PWG instances.
21+
*
22+
* Typical use: stepper motor control where STEP and DIR pins on the
23+
* same port need precisely timed pulse sequences.
24+
*/
25+
26+
#pragma once
27+
28+
#include <stdint.h>
29+
#include <stddef.h>
30+
31+
/**
32+
* Callback invoked by the PWG fill thread whenever a half-buffer
33+
* needs new data.
34+
*
35+
* @param opaque User-supplied context pointer
36+
* @param buf Pointer to the half-buffer to fill (32 entries)
37+
* @param count Number of uint32_t entries to write
38+
*
39+
* Each entry is a BSRR value that will be written to the GPIO port
40+
* on successive timer ticks. The callback must fill all 'count'
41+
* entries before returning.
42+
*/
43+
typedef void (*pwg_fill_cb)(void *opaque, uint32_t *buf, size_t count);

src/platform/stm32/stm32_pwg.c

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// This file is not compiled on its own but needs to be included
2+
// by a stm32 chip specific file
3+
4+
#include <mios/pwg.h>
5+
6+
#include <malloc.h>
7+
#include <stdlib.h>
8+
#include <stdio.h>
9+
10+
#include "cache.h"
11+
12+
#define PWG_BUF_SIZE 64
13+
#define PWG_HALF_SIZE (PWG_BUF_SIZE / 2)
14+
15+
typedef struct stm32_pwg {
16+
17+
// DMA buffer must be cache-line aligned and not share cache lines
18+
// with other fields, since we use dcache_op(DCACHE_CLEAN) on it.
19+
uint32_t buf[PWG_BUF_SIZE];
20+
21+
uint32_t timer_base;
22+
uint32_t gpio_bsrr_addr;
23+
stm32_dma_instance_t dma;
24+
task_waitable_t waitq;
25+
pwg_fill_cb fill_cb;
26+
void *opaque;
27+
uint8_t active_half;
28+
uint8_t running;
29+
30+
} stm32_pwg_t;
31+
32+
33+
static void
34+
pwg_dma_cb(stm32_dma_instance_t inst, uint32_t status, void *arg)
35+
{
36+
stm32_pwg_t *pwg = arg;
37+
38+
if(status & DMA_STATUS_HALF_XFER)
39+
pwg->active_half = 1;
40+
41+
if(status & DMA_STATUS_FULL_XFER)
42+
pwg->active_half = 0;
43+
44+
task_wakeup_sched_locked(&pwg->waitq, 0);
45+
}
46+
47+
48+
static void *
49+
pwg_thread(void *arg)
50+
{
51+
stm32_pwg_t *pwg = arg;
52+
53+
pwg->fill_cb(pwg->opaque, &pwg->buf[0], PWG_HALF_SIZE);
54+
pwg->fill_cb(pwg->opaque, &pwg->buf[PWG_HALF_SIZE], PWG_HALF_SIZE);
55+
dcache_op(pwg->buf, sizeof(pwg->buf), DCACHE_CLEAN);
56+
57+
stm32_dma_start(pwg->dma);
58+
reg_wr(pwg->timer_base + TIMx_CR1, 1);
59+
60+
while(pwg->running) {
61+
62+
int q = irq_forbid(IRQ_LEVEL_SCHED);
63+
task_sleep_sched_locked(&pwg->waitq);
64+
uint8_t active = pwg->active_half;
65+
irq_permit(q);
66+
67+
uint32_t *half = active ?
68+
&pwg->buf[0] : &pwg->buf[PWG_HALF_SIZE];
69+
pwg->fill_cb(pwg->opaque, half, PWG_HALF_SIZE);
70+
dcache_op(half, PWG_HALF_SIZE * sizeof(uint32_t), DCACHE_CLEAN);
71+
}
72+
73+
reg_wr(pwg->timer_base + TIMx_CR1, 0);
74+
stm32_dma_stop(pwg->dma);
75+
return NULL;
76+
}
77+
78+
79+
static stm32_pwg_t *
80+
stm32_pwg_init(uint32_t timer_base,
81+
uint16_t timer_clkid,
82+
uint32_t dma_resource_id,
83+
uint32_t gpio_bsrr_addr,
84+
uint32_t frequency,
85+
pwg_fill_cb fill_cb,
86+
void *opaque)
87+
{
88+
stm32_pwg_t *pwg = xalloc(sizeof(stm32_pwg_t), CACHE_LINE_SIZE,
89+
MEM_TYPE_DMA | MEM_CLEAR);
90+
91+
pwg->timer_base = timer_base;
92+
pwg->gpio_bsrr_addr = gpio_bsrr_addr;
93+
pwg->fill_cb = fill_cb;
94+
pwg->opaque = opaque;
95+
pwg->running = 1;
96+
97+
task_waitable_init(&pwg->waitq, "pwg");
98+
99+
// Configure timer
100+
101+
clk_enable(timer_clkid);
102+
103+
uint32_t tclk = clk_get_freq(timer_clkid);
104+
uint32_t div = tclk / frequency;
105+
uint32_t psc = 0;
106+
107+
while(div > 65536) {
108+
psc++;
109+
div = tclk / (frequency * (psc + 1));
110+
}
111+
112+
reg_wr(timer_base + TIMx_PSC, psc);
113+
reg_wr(timer_base + TIMx_ARR, div - 1);
114+
reg_wr(timer_base + TIMx_EGR, 1); // Generate update to load PSC/ARR
115+
reg_wr(timer_base + TIMx_DIER, 1 << 8); // UDE - Update DMA Enable
116+
117+
// Configure DMA
118+
119+
pwg->dma = stm32_dma_alloc(dma_resource_id, "pwg");
120+
121+
stm32_dma_config(pwg->dma,
122+
STM32_DMA_BURST_NONE,
123+
STM32_DMA_BURST_NONE,
124+
STM32_DMA_PRIO_HIGH,
125+
STM32_DMA_32BIT,
126+
STM32_DMA_32BIT,
127+
STM32_DMA_INCREMENT,
128+
STM32_DMA_FIXED,
129+
STM32_DMA_CIRCULAR,
130+
STM32_DMA_M_TO_P);
131+
132+
stm32_dma_set_paddr(pwg->dma, gpio_bsrr_addr);
133+
stm32_dma_set_mem0(pwg->dma, pwg->buf);
134+
stm32_dma_set_nitems(pwg->dma, PWG_BUF_SIZE);
135+
136+
stm32_dma_set_callback(pwg->dma, pwg_dma_cb, pwg, IRQ_LEVEL_SCHED,
137+
DMA_STATUS_HALF_XFER | DMA_STATUS_FULL_XFER);
138+
139+
thread_create(pwg_thread, pwg, 512, "pwg", TASK_DETACHED, 20);
140+
141+
return pwg;
142+
}
143+
144+
145+
void
146+
stm32_pwg_stop(stm32_pwg_t *pwg)
147+
{
148+
pwg->running = 0;
149+
task_wakeup(&pwg->waitq, 0);
150+
}

src/platform/stm32h7/stm32h7.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ SRCS += ${C}/systick.c \
2222
${P}/stm32h7_spi.c \
2323
${P}/stm32h7_adc.c \
2424
${P}/stm32h7_can.c \
25+
${P}/stm32h7_pwg.c \
2526
${P}/stm32h7_systim.c \
2627
${P}/stm32h7_idle.c \
2728
${P}/stm32h7_info.c \

src/platform/stm32h7/stm32h7_pwg.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <mios/task.h>
2+
#include <mios/type_macros.h>
3+
4+
#include "stm32h7_clk.h"
5+
#include "stm32h7_tim.h"
6+
#include "stm32h7_dma.h"
7+
#include "stm32h7_pwg.h"
8+
9+
#include "irq.h"
10+
11+
#define GPIO_PORT_ADDR(x) (0x58020000 + ((x) * 0x400))
12+
13+
#include "platform/stm32/stm32_pwg.c"
14+
15+
16+
static const struct {
17+
uint32_t base;
18+
uint16_t clkid;
19+
uint8_t dma_resource;
20+
} pwg_timers[] = {
21+
[1] = { TIM1_BASE, CLK_TIM1, 15 },
22+
[2] = { TIM2_BASE, CLK_TIM2, 22 },
23+
[3] = { TIM3_BASE, CLK_TIM3, 27 },
24+
[4] = { TIM4_BASE, CLK_TIM4, 32 },
25+
[5] = { TIM5_BASE, CLK_TIM5, 59 },
26+
[6] = { TIM6_BASE, CLK_TIM6, 69 },
27+
[7] = { TIM7_BASE, CLK_TIM7, 70 },
28+
[8] = { TIM8_BASE, CLK_TIM8, 51 },
29+
};
30+
31+
32+
stm32_pwg_t *
33+
stm32h7_pwg_create(unsigned int timer_instance,
34+
uint8_t gpio_port,
35+
uint32_t frequency,
36+
pwg_fill_cb fill_cb,
37+
void *opaque)
38+
{
39+
if(timer_instance < 1 || timer_instance >= ARRAYSIZE(pwg_timers))
40+
return NULL;
41+
42+
return stm32_pwg_init(pwg_timers[timer_instance].base,
43+
pwg_timers[timer_instance].clkid,
44+
pwg_timers[timer_instance].dma_resource,
45+
GPIO_PORT_ADDR(gpio_port) + 0x18,
46+
frequency,
47+
fill_cb,
48+
opaque);
49+
}

src/platform/stm32h7/stm32h7_pwg.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
#include <mios/pwg.h>
4+
5+
typedef struct stm32_pwg stm32_pwg_t;
6+
7+
stm32_pwg_t *stm32h7_pwg_create(unsigned int timer_instance,
8+
uint8_t gpio_port,
9+
uint32_t frequency,
10+
pwg_fill_cb fill_cb,
11+
void *opaque);
12+
13+
void stm32_pwg_stop(stm32_pwg_t *pwg);

src/platform/stm32h7/stm32h7_tim.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#define TIM1_BASE 0x40010000
34
#define TIM2_BASE 0x40000000
45
#define TIM3_BASE 0x40000400
56
#define TIM4_BASE 0x40000800

0 commit comments

Comments
 (0)