PWM repeatedly switches a pin at a selected frequency. It is useful for LED brightness, buzzers, and devices that accept a duty-controlled input.
Fade The Built-In LED
channel is pwm.open: $led_builtin, 1000
repeat 21 as i [
pwm.write: channel, i * 500
wait: 30
]
pwm.write: channel, 0
pwm.close: channel
set channel to nil
The electrical brightness direction follows the LED’s polarity. On an active-low built-in LED, a larger duty may look dimmer rather than brighter.
Word Table
| Word | Result | Use |
|---|---|---|
pwm.open | Handle | Open a channel on a pin at a frequency in Hz |
pwm.write | nil | Set duty from 0 through 10000 |
pwm.close | nil | Stop output and release the handle |
Frequency And Duty
channel is pwm.open: 4, 5000
pwm.write: channel, 2500 -- 25 percent duty
pwm.write: channel, 7500 -- 75 percent duty
The duty scale is always Frothy’s inclusive 0..10000 contract. Frequency is
specified in hertz. The target maps those values onto its hardware PWM
peripheral; unsupported pins, frequencies, or exhausted channels return an
error.
pwm.write changes the duty of an open channel. Values below 0 or above
10000 are rejected rather than clamped.
Which Pins Can PWM
Any output-capable GPIO can carry PWM — the chip routes its PWM peripheral to the pin, so there is no special “PWM pin” set the way analog input is tied to particular converter pins. Two limits apply on every board:
- Input-only pins can’t. A pin with no output driver rejects
pwm.open. Which pins those are is a property of your board — on the classic ESP32 DevKit V1, for example, GPIO 34, 35, 36, and 39 are input-only. - Never drive the flash pins. The pins wired to the module’s SPI flash
chip count as output-capable, so
pwm.openaccepts them — but driving them corrupts the firmware’s own flash bus and typically crashes the board. Check your board’s pinout for which to avoid (on classic ESP32 modules, for example, GPIO 6–11).
A pin already held by an open channel reports busy for a different
frequency, and plain gpio.write / gpio.mode on it also report busy
(a digital write would silently detach the pin from its PWM signal). An
exact repeat of the same pwm.open returns the existing handle. Hardware
channels are limited — four concurrent channels on current boards — so
pwm.open beyond that returns an error until one is closed.
Handle Lifetime
pwm.open returns a volatile Handle. Close it before reusing the pin or
saving the project, then replace every top-level handle binding with a durable
value such as false or nil.
channel is false
to dimmer.open [
set channel to pwm.open: 4, 1000
]
to dimmer.close [
pwm.close: channel
set channel to false
]
boot is fn [ dimmer.open: ]
For precisely timed, finite high/low spans rather than a repeating duty cycle, use Digital signals . The Fade an LED tutorial develops a complete PWM project.