or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

analog-io.mdbitbangio.mdboard-pins.mdcommunication.mdcore-framework.mddigital-io.mdindex.mdperipherals.mdpwm-pulse.mdutilities.md

pwm-pulse.mddocs/

0

# PWM and Pulse Control

1

2

Pulse Width Modulation output and pulse measurement capabilities for motor control, servo positioning, and signal generation. Provides CircuitPython-compatible PWM and pulse measurement operations with automatic platform detection and driver loading.

3

4

## Capabilities

5

6

### PWM Output

7

8

The PWMOut class provides pulse width modulation output for controlling motors, servos, LEDs, and other devices that respond to duty cycle control. Automatically detects and uses appropriate platform-specific drivers.

9

10

```python { .api }

11

class PWMOut(ContextManaged):

12

def __init__(self, pin, *, duty_cycle: int = 0, frequency: int = 500, variable_frequency: bool = False):

13

"""

14

Initialize PWM output pin.

15

16

Args:

17

pin: Board PWM-capable pin object (from board module)

18

duty_cycle: Initial PWM duty cycle (0-65535, default 0)

19

frequency: PWM frequency in Hz (default 500)

20

variable_frequency: Enable variable frequency support (not implemented)

21

22

Raises:

23

RuntimeError: If no PWM channel found for the pin

24

ValueError: If PWM channel does not exist or modules not loaded

25

"""

26

27

@property

28

def duty_cycle(self) -> int:

29

"""

30

Get or set PWM duty cycle.

31

32

Returns:

33

int: Current duty cycle (0-65535, where 65535 = 100%)

34

"""

35

36

@duty_cycle.setter

37

def duty_cycle(self, value: int) -> None:

38

"""

39

Set PWM duty cycle.

40

41

Args:

42

value: Duty cycle value (0-65535)

43

44

Raises:

45

TypeError: If value is not int or float

46

ValueError: If value is outside 0.0-1.0 range (internally converted)

47

"""

48

49

@property

50

def frequency(self) -> float:

51

"""

52

Get or set PWM frequency in Hz.

53

54

Returns:

55

float: Current PWM frequency

56

"""

57

58

@frequency.setter

59

def frequency(self, value: float) -> None:

60

"""

61

Set PWM frequency.

62

63

Args:

64

value: Frequency in Hz

65

66

Raises:

67

TypeError: If value is not int or float

68

"""

69

70

@property

71

def period(self) -> float:

72

"""

73

Get or set PWM period in seconds.

74

75

Returns:

76

float: Current PWM period (1/frequency)

77

"""

78

79

@period.setter

80

def period(self, value: float) -> None:

81

"""

82

Set PWM period.

83

84

Args:

85

value: Period in seconds

86

87

Raises:

88

TypeError: If value is not int or float

89

"""

90

91

def deinit(self) -> None:

92

"""Release PWM resources and disable output"""

93

```

94

95

### Pulse Input Measurement

96

97

The PulseIn class provides pulse width measurement for reading PWM signals, IR remote controls, and other pulse-based communications. Limited platform support.

98

99

```python { .api }

100

class PulseIn(ContextManaged):

101

def __init__(self, pin, maxlen: int = 2, idle_state: bool = False):

102

"""

103

Initialize pulse input measurement.

104

105

Args:

106

pin: Board pin object for pulse measurement

107

maxlen: Maximum number of pulses to store (default 2)

108

idle_state: Expected idle state - False for low, True for high

109

110

Raises:

111

RuntimeError: If platform not supported or setup failed

112

"""

113

114

@property

115

def maxlen(self) -> int:

116

"""

117

Maximum number of pulses stored.

118

119

Returns:

120

int: Maximum pulse buffer length

121

"""

122

123

@property

124

def paused(self) -> bool:

125

"""

126

True if pulse capture is paused.

127

128

Returns:

129

bool: Paused state

130

"""

131

132

def __len__(self) -> int:

133

"""

134

Number of pulses currently captured.

135

136

Returns:

137

int: Current number of stored pulses

138

"""

139

140

def __getitem__(self, index: int) -> int:

141

"""

142

Get pulse duration by index.

143

144

Args:

145

index: Pulse index (0 = oldest)

146

147

Returns:

148

int: Pulse duration in microseconds

149

150

Raises:

151

IndexError: If index is out of range

152

"""

153

154

def clear(self) -> None:

155

"""Clear all captured pulses"""

156

157

def popleft(self) -> int:

158

"""

159

Remove and return oldest pulse.

160

161

Returns:

162

int: Duration of oldest pulse in microseconds

163

164

Raises:

165

IndexError: If no pulses available

166

"""

167

168

def pause(self) -> None:

169

"""Pause pulse capture"""

170

171

def resume(self, trigger_duration: int = 0) -> None:

172

"""

173

Resume pulse capture.

174

175

Args:

176

trigger_duration: Optional trigger pulse duration in microseconds

177

"""

178

179

def deinit(self) -> None:

180

"""Release pulse measurement resources"""

181

```

182

183

## Usage Examples

184

185

### Basic PWM LED Control

186

187

```python

188

import board

189

import pwmio

190

import time

191

192

# Create PWM output

193

led = pwmio.PWMOut(board.D18, frequency=1000, duty_cycle=0)

194

195

# Fade in

196

for i in range(100):

197

led.duty_cycle = int(i * 655.35) # 0 to 65535

198

time.sleep(0.01)

199

200

# Fade out

201

for i in range(100, 0, -1):

202

led.duty_cycle = int(i * 655.35)

203

time.sleep(0.01)

204

205

# Cleanup

206

led.deinit()

207

```

208

209

### Servo Motor Control

210

211

```python

212

import board

213

import pwmio

214

import time

215

216

# Standard servo control (50Hz, 1-2ms pulse width)

217

servo = pwmio.PWMOut(board.D18, frequency=50)

218

219

def set_servo_angle(servo, angle):

220

"""Set servo angle (0-180 degrees)"""

221

# Convert angle to duty cycle

222

# 1ms = 5% duty cycle, 2ms = 10% duty cycle at 50Hz

223

min_duty = int(0.05 * 65535) # 1ms pulse

224

max_duty = int(0.10 * 65535) # 2ms pulse

225

duty_range = max_duty - min_duty

226

227

duty_cycle = min_duty + int((angle / 180.0) * duty_range)

228

servo.duty_cycle = duty_cycle

229

230

# Sweep servo back and forth

231

for _ in range(3):

232

# Move to 0 degrees

233

set_servo_angle(servo, 0)

234

time.sleep(1)

235

236

# Move to 90 degrees

237

set_servo_angle(servo, 90)

238

time.sleep(1)

239

240

# Move to 180 degrees

241

set_servo_angle(servo, 180)

242

time.sleep(1)

243

244

servo.deinit()

245

```

246

247

### PWM Motor Speed Control

248

249

```python

250

import board

251

import pwmio

252

import digitalio

253

import time

254

255

# Motor control with PWM speed and direction

256

motor_pwm = pwmio.PWMOut(board.D18, frequency=1000)

257

motor_dir = digitalio.DigitalInOut(board.D19)

258

motor_dir.direction = digitalio.Direction.OUTPUT

259

260

def set_motor_speed(pwm, direction, speed_percent):

261

"""Set motor speed (-100 to +100 percent)"""

262

if speed_percent < 0:

263

direction.value = False # Reverse

264

speed_percent = -speed_percent

265

else:

266

direction.value = True # Forward

267

268

# Convert percentage to duty cycle

269

duty_cycle = int((speed_percent / 100.0) * 65535)

270

pwm.duty_cycle = duty_cycle

271

272

# Motor control demo

273

speeds = [25, 50, 75, 100, 0, -25, -50, -75, -100, 0]

274

275

for speed in speeds:

276

set_motor_speed(motor_pwm, motor_dir, speed)

277

print(f"Motor speed: {speed}%")

278

time.sleep(2)

279

280

# Cleanup

281

motor_pwm.deinit()

282

motor_dir.deinit()

283

```

284

285

### Variable Duty Cycle with Context Manager

286

287

```python

288

import board

289

import pwmio

290

import time

291

import math

292

293

# Using context manager for automatic cleanup

294

with pwmio.PWMOut(board.D18, frequency=500) as pwm:

295

# Generate sine wave pattern

296

for i in range(360):

297

# Convert angle to sine wave (0-1 range)

298

sine_val = (math.sin(math.radians(i)) + 1) / 2

299

300

# Convert to duty cycle

301

pwm.duty_cycle = int(sine_val * 65535)

302

303

time.sleep(0.01) # ~10Hz update rate

304

```

305

306

### Pulse Input Measurement

307

308

```python

309

# Note: PulseIn has limited platform support (Raspberry Pi, some Odroid boards)

310

import board

311

import pulseio

312

import time

313

314

try:

315

# Initialize pulse measurement

316

pulse_in = pulseio.PulseIn(board.D2, maxlen=10, idle_state=False)

317

318

print("Measuring pulses... Press Ctrl+C to stop")

319

320

while True:

321

# Wait for pulses

322

while len(pulse_in) == 0:

323

time.sleep(0.01)

324

325

# Read and display pulses

326

while len(pulse_in) > 0:

327

pulse_duration = pulse_in.popleft()

328

print(f"Pulse: {pulse_duration} microseconds")

329

330

time.sleep(0.1)

331

332

except KeyboardInterrupt:

333

print("Stopping pulse measurement")

334

except RuntimeError as e:

335

print(f"PulseIn not supported on this platform: {e}")

336

finally:

337

try:

338

pulse_in.deinit()

339

except:

340

pass

341

```

342

343

### PWM Frequency Sweep

344

345

```python

346

import board

347

import pwmio

348

import time

349

350

# Create PWM with variable frequency

351

pwm = pwmio.PWMOut(board.D18, duty_cycle=32768) # 50% duty cycle

352

353

# Sweep frequency from 100Hz to 2000Hz

354

frequencies = [100, 200, 500, 1000, 1500, 2000]

355

356

for freq in frequencies:

357

pwm.frequency = freq

358

print(f"PWM frequency: {freq} Hz")

359

time.sleep(2)

360

361

# Reset to default

362

pwm.frequency = 500

363

pwm.duty_cycle = 0

364

365

pwm.deinit()

366

```

367

368

## Platform Considerations

369

370

### PWM Support

371

372

**Widely Supported Platforms:**

373

- **Raspberry Pi**: Hardware PWM with lgpio (Pi 5) or RPi.GPIO (Pi 4 and earlier)

374

- **Linux SBCs**: Generic sysfs PWM interface (BeagleBone, Banana Pi, Odroid, etc.)

375

- **Specialized Hardware**: Binho Nova, GreatFET One, FTDI adapters

376

- **RP2040 Boards**: Native PWM support via U2IF protocol

377

378

**Platform-Specific Notes:**

379

- **BeagleBone**: Custom sysfs implementation for AM335x SoCs

380

- **Jetson Boards**: Tegra-specific PWM drivers

381

- **Rock Pi**: Rockchip SoC PWM support

382

- **Siemens IoT2000**: AM65xx PWM implementation

383

384

### Pulse Input Limitations

385

386

**Supported Platforms:**

387

- **Raspberry Pi**: All models with libgpiod-based implementation

388

- **Odroid C4/N2**: Amlogic G12 SoC support

389

390

**Requirements:**

391

- libgpiod library must be installed

392

- Requires compiled helper binaries (libgpiod_pulsein64/libgpiod_pulsein)

393

- Uses inter-process communication via System V message queues

394

395

### Performance Notes

396

397

- **PWM Duty Cycle**: Hardware PWM provides precise timing

398

- **Software PWM**: May have timing variations under CPU load

399

- **Frequency Limits**: Platform-dependent, typically 1Hz to several kHz

400

- **Resolution**: 16-bit duty cycle resolution (0-65535)

401

402

### Error Handling

403

404

```python

405

import board

406

import pwmio

407

408

try:

409

pwm = pwmio.PWMOut(board.D18, frequency=1000)

410

pwm.duty_cycle = 32768 # 50%

411

# Use PWM...

412

pwm.deinit()

413

except RuntimeError as e:

414

if "PWM channel" in str(e):

415

print(f"PWM not available on this pin: {e}")

416

else:

417

print(f"PWM error: {e}")

418

except ValueError as e:

419

print(f"PWM configuration error: {e}")

420

```