Skip to main content

PWM measurement and generation

Four GPIO pins can measure an external PWM signal or generate a square wave. These pins belong to the same two channels as the SPI, I2C and UART interfaces, so PWM follows the same sharing rules as those interfaces.

Pin roles​

The role of each pin is fixed by the hardware:

GPIORoleChannel
DA17PWM input (measurement)0
DA18PWM output (square wave, about 50 % duty)0
DA21PWM input (measurement)1
DA22PWM output (square wave, about 50 % duty)1

Any other pin is rejected with a 400 error. A PWM handle is obtained with gpio.pwm(io):

const pwm_in = tester.gpio.pwm(17);
const pwm_out = tester.gpio.pwm(18);

Channel sharing​

Each channel serves one function at a time. Channel 0 is DA16 to DA19 and channel 1 is DA20 to DA23 (see pin alternate functions). The two PWM pins of a channel are DA17 with DA18 and DA21 with DA22.

  • Enabling PWM fails with 409 CONFIGURATION_CONFLICT while I2C, SPI or UART is enabled on the same channel, or while the other PWM pin of that channel is enabled.
  • In the other direction, enabling I2C, SPI or UART on the channel, or configuring the pin as a GPIO output, fails with 409 CONFIGURATION_CONFLICT while PWM is enabled.
  • POST /com/reset (tester.com.reset()) leaves PWM running. POST /gpio/reset (tester.gpio.reset()) stops it and restores the PWM defaults.

DA18 and DA21 are on different channels, so an output on DA18 and an input on DA21 can run at the same time.

Supply voltage (vcc)​

vcc sets the target supply of the pin's level shifter, from 1.8 V to 3.3 V inclusive. The default is 3.3 V.

  • On an output, vcc is the high level driven on the pin.
  • On an input, vcc is the highest voltage the pin accepts.

vcc does not move the input switching thresholds. They stay at about 0.8 V to 1.35 V.

Pin-voltage safety check​

Enabling PWM, or changing vcc while PWM runs, first measures the pin. If it reads below −0.5 V or above vcc + 0.5 V, the request fails with 409 UNSAFE_PIN_VOLTAGE and PWM stays disabled. The error message gives the voltage measured and the allowed window.

Generating a square wave​

An output pin generates a square wave of about 50 % duty cycle.

  • Requested frequencies range from 91.55 Hz to 7.5 MHz.
  • The device generates the closest average frequency it can produce and returns it in the frequency field of the configuration. Read this value back rather than assuming the requested one.
  • The generated frequency is 8 × 30 MHz / (8d + 9), or 8 × 6 MHz / (8d + 9) below about 458 Hz, where d is an integer divisor.
  • Every eighth low phase is one clock period longer than the others. The average frequency therefore sits slightly below round requests (about −0.4 % at 1 MHz), and the duty cycle is slightly under 50 % (49.8 % at 1 MHz, 49 % at 5 MHz).

The output runs until it is disabled, until POST /gpio/reset, or until duration seconds have passed. duration is in (0, 86400] s. Without a duration, the output runs until it is disabled.

Changing the frequency or duration of a running output restarts it. The pin's level shifter is off for about 100 ms during the restart, and duration counts from the change.

The following example generates 1 kHz at 3.3 V on DA18 for 10 s and prints the frequency actually generated:

const pwm_out = tester.gpio.pwm(18);
const config = await pwm_out.configure_output({ frequency: 1000, vcc: 3.3, duration: 10 });
console.log(`Generating ${config.frequency} Hz`);

Measuring a PWM signal​

An input pin is sampled at 30 MHz. Each call to measure() runs one capture:

  • The capture runs until it holds at least one full cycle (rising, falling, then rising edge) and 100 ms have passed. It ends on a rising edge.
  • timeout (default 5 s, range 0.1 s to 60 s) ends the capture whatever edges were seen.
  • status is "ok" when at least one full cycle was captured, otherwise "timeout".
  • Only "ok" results carry the statistics (frequency, duty cycle, period and high/low times). They cover whole cycles from the first to the last captured rising edge.
  • Every result carries level, the line level at the end of the capture, plus edges, cycles and duration.
Accuracy limit

Signals up to 5 MHz are measured accurately. Above about 6 MHz (pulses shorter than about 67 ns), the samples alias silently and the result is wrong without any error.

If the AT1000 could not keep up with a capture, the request returns a 500 error. Retry the measurement.

The following example enables measurement on DA17 at 3.3 V and runs a capture of at most 2 s:

const pwm_in = tester.gpio.pwm(17);
await pwm_in.configure_input({ vcc: 3.3 });

const m = await pwm_in.measure({ timeout: 2 });
if (m.status === 'ok') {
console.log(`Frequency: ${m.frequency} Hz, duty cycle: ${m.duty_cycle} %`);
} else {
console.log(`No full cycle within 2 s, line is ${m.level ? 'high' : 'low'}`);
}

Loopback test​

With DA18 wired to DA21, the AT1000 can measure its own output. The two pins are on different channels, so they do not conflict.

const pwm_out = tester.gpio.pwm(18);
const pwm_in = tester.gpio.pwm(21);

const config = await pwm_out.configure_output({ frequency: 100000, vcc: 3.3 });
await pwm_in.configure_input({ vcc: 3.3 });

const m = await pwm_in.measure();
if (m.status === 'ok') {
console.log(`Generated ${config.frequency} Hz, measured ${m.frequency} Hz, duty ${m.duty_cycle} %`);
}

await pwm_out.disable();
await pwm_in.disable();

Handling a channel conflict​

I2C 0 uses channel 0, so PWM on DA17 cannot be enabled while I2C 0 is enabled. The device answers with the code CONFIGURATION_CONFLICT:

import { ApiError } from '@ikalogic/at1000';

await tester.com.i2c(0).enable({ baud_rate: 400000, vcc: 3.3 });

try {
await tester.gpio.pwm(17).configure_input({ vcc: 3.3 });
} catch (err) {
if (err instanceof ApiError && err.code === 'CONFIGURATION_CONFLICT') {
console.log(`Channel busy: ${err.detail}`);
} else {
throw err;
}
}

UNSAFE_PIN_VOLTAGE is handled the same way.

Realtime event​

Every change to a PWM configuration emits a gpio.pwm_config event. Its id is the pin (17, 18, 21 or 22) and its data is the PWM configuration. The event is also emitted when a timed output ends. See realtime events.

REST API​

The SDK methods map to three endpoints. Requests that change the device require the session token.

MethodPathBodyResponseSession token
GET/v0/gpio/{io_id}/pwm/config-IOPwmConfignot required
PATCH/v0/gpio/{io_id}/pwm/configIOPwmConfigPatchIOPwmConfigrequired
POST/v0/gpio/{io_id}/pwm/measureIOPwmMeasureRequest, may be {}IOPwmMeasurementrequired

io_id is 17 or 21 for an input and 18 or 22 for an output. measure accepts only 17 and 21.

IOPwmConfig​

FieldTypeDescription
enabledbooleanWhether PWM is enabled on the pin
direction"input" | "output"Role of the pin
vccnumberLevel-shifter supply in volts, 1.8 to 3.3
frequencynumber, optionalOutputs only: the frequency generated, in Hz, after quantization
durationnumber, optionalOutputs only: the run time in seconds requested at the last start. Absent when the output runs until disabled
{ "enabled": true, "direction": "output", "vcc": 3.3, "frequency": 999.9958333506944, "duration": 10 }

IOPwmConfigPatch​

All fields are optional. Omitted fields keep their value.

FieldTypeDescription
enabledbooleanEnables or disables PWM
vccnumber1.8 to 3.3 V
frequencynumberOutputs only: requested frequency, 91.55 Hz to 7.5 MHz
durationnumber | nullOutputs only: run time in seconds, in (0, 86400], counted from this request. null clears it, so the output runs until disabled
{ "enabled": true, "vcc": 3.3, "frequency": 1000, "duration": 10 }

IOPwmMeasureRequest​

FieldTypeDescription
timeoutnumber, optionalLongest capture in seconds, 0.1 to 60. Default 5

IOPwmMeasurement​

The result is a discriminated union on status. All times are in seconds.

FieldPresentDescription
statusalways"ok" when at least one full cycle was captured, otherwise "timeout"
levelalwaysLine level at the end of the capture
edgesalwaysNumber of edges captured
cyclesalwaysNumber of whole cycles captured
durationalwaysCaptured time
frequency"ok" onlyAverage frequency in Hz
duty_cycle"ok" onlyDuty cycle in percent
period, period_min, period_max"ok" onlyAverage, shortest and longest period
high_time, high_time_min, high_time_max"ok" onlyAverage, shortest and longest high time
low_time, low_time_min, low_time_max"ok" onlyAverage, shortest and longest low time
{ "status": "timeout", "level": false, "edges": 0, "cycles": 0, "duration": 2.0 }

Errors​

Errors use the standard { code, message, suggestion, path } body.

HTTPcodeWhen it fires
400INVALID_VALUEThe pin does not support PWM or does not have this role, a value is out of range, or frequency/duration is sent for an input
400INVALID_CONFIGURATIONmeasure was called while PWM is disabled on the pin
409CONFIGURATION_CONFLICTI2C, SPI, UART or the other PWM pin is enabled on the same channel (see channel sharing)
409UNSAFE_PIN_VOLTAGEThe pin failed the voltage safety check
500INTERNAL_ERRORThe AT1000 could not keep up with the capture. Retry the measurement

The full method reference is in the GPIO API reference.