Skip to content

Commit

Permalink
ESP32: Per-pin PWM/LEDC frequency and resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
vickash committed Sep 22, 2024
1 parent 7595156 commit 0081907
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 20 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@
- Starts with mode as `:output` instead of `:output_pwm`, conserving PWM channels.
- Mode doesn't change until first call to `#pwm_write`.
- Calling only `#digital_write` explicitly will keep it in "digital mode".
- Set resolution and frequency per `PWMOutput` instance (pin), instead of per `Board`:
- `#initialize` hash now accepts `frequency:` and `resolution:` keys.
- Or call `#pwm_enable` explicitly with `frequency:` and `resolution:` kwargs.
- Only on ESP32 for now. Defaults to 1 kHz and 8-bit when not given.

- `SPI::Peripheral`:
- Split into `SPI:Peripheral::SinglePin` and `Spi::Peripheral::MultiPin` to allow modeling more complex peripherals.
Expand Down Expand Up @@ -124,6 +128,8 @@

### Board Interface Changes

- `Board#set_pin_mode` now takes a third argument, an `options={}` hash. Only used keys are `resolution:` and `frequency:` for setting PWM resolution. Only works on ESP32 boards so far.

- Added `Board#set_pin_debounce`
- Implemented for Linux GPIO alerts in `Denko::PiBoard` (denko-piboard gem).
- Sets a time (in microseconds) that level changes on a pin must be stable for, before an update happens.
Expand Down
27 changes: 27 additions & 0 deletions examples/pulse_io/pwm_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#
# Example configuring PWM frequency and resolution. Only on ESP32 for now.
# Capture output with logic analyzer or oscilloscope to verify accuracy.
#
require 'bundler/setup'
require 'denko'

board = Denko::Board.new(Denko::Connection::Serial.new)
pwm = Denko::PulseIO::PWMOutput.new(board: board, pin: 2)

# Resolution test: ~1% duty at 1 kHz / 10-bit.
# On and off approx. 10us and 990us respectively.
pwm.pwm_enable(frequency: 1_000, resolution: 10)
pwm.write 10 # of 1023
sleep 0.5
pwm.digital_write 0

sleep 0.5

# Frequency test: ~50% duty at 10 kHz / 8-bit.
# On and off both approx. 50us.
pwm.pwm_enable(frequency: 10_000, resolution: 8)
pwm.write 127 # of 255
sleep 0.5
pwm.digital_write 0

board.finish_write
14 changes: 7 additions & 7 deletions src/lib/Denko.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ class Denko {
void dacWrite (byte p, int v, boolean echo=true); //cmd = 4
int aRead (byte p); //cmd = 5
void setListener (byte p, boolean enabled, byte analog, byte exponent, boolean local=true); //cmd = 6

// Read value of micros() every loop.
unsigned long currentTime;

// Counts 1ms ticks based on currentTime. Rolls over every 256ms.
byte tickCount;

Expand All @@ -46,7 +46,7 @@ class Denko {
void findLastActiveListener();

#ifdef ESP32
// Use 8 bits for PWM width to match everything else.
// 8-bit global PWM resolution by default, to match other boards. Reconfigurable.
uint8_t esp32AnalogWRes = 8;
void ledcDetachAll();
#endif
Expand Down Expand Up @@ -119,7 +119,7 @@ class Denko {
void showLEDArray (); //cmd = 19

// Bit Bang SPI
void spiBBtransfer (uint8_t clock, uint8_t input, uint8_t output, uint8_t select, uint8_t settings,
void spiBBtransfer (uint8_t clock, uint8_t input, uint8_t output, uint8_t select, uint8_t settings,
uint8_t rLength, uint8_t wLength, byte *data); //cmd = 21
byte spiBBtransferByte (uint8_t clock, uint8_t input, uint8_t output, uint8_t select,
uint8_t mode, uint8_t bitOrder, byte data);
Expand All @@ -132,7 +132,7 @@ class Denko {
void spiTransfer (uint32_t clockRate, uint8_t select, uint8_t settings, uint8_t rLength, uint8_t wLength, byte *data); //cmd = 26
void spiAddListener (); //cmd = 27
void spiReadListener (uint8_t i);

// SPI Listeners (both hardware and bit bang)
void spiRemoveListener (); //cmd = 28
void spiUpdateListeners ();
Expand Down Expand Up @@ -221,7 +221,7 @@ class Denko {

//
// Shared SPI listeners that can be used by either hardware or bit bang SPI.
//
//
#if defined(DENKO_SPI) || defined(DENKO_SPI_BB)
// How many SPI lsiteners.
#define SPI_LISTENER_COUNT 4
Expand All @@ -244,7 +244,7 @@ class Denko {
uint8_t i2c_bb_quarter_period = 1;

// Internal stuff
void i2c_bb_delay_quarter_period();
void i2c_bb_delay_quarter_period();
void i2c_bb_delay_half_period();
void i2c_bb_sda_high ();
void i2c_bb_sda_low ();
Expand Down
35 changes: 22 additions & 13 deletions src/lib/DenkoCoreIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ void Denko::setMode(byte p, byte m) {
// Use the lowest 4 bits of m to set different input/output modes.
// Also enables/disables peripherals for certain targets.
//
// OUTPUT MODES:
// OUTPUT MODES:
// 0000 = Digital Output
// 0010 = PWM Ouptut
// 0100 = DAC Output
Expand All @@ -21,38 +21,49 @@ void Denko::setMode(byte p, byte m) {
m = m & 0b00001111;

#if defined(ESP32)
// Free the LEDC channel if leaving PWM mode.
if (m != 0b0010) ledcDetach(p);

// Disable attached DAC if leaving DAC mode.
#if defined(SOC_DAC_SUPPORTED)
if (m != 0b0100) dacDisable(p);
if (m != 0b0100) dacDisable(p);
#endif

// Attach or detach LEDC channel whether entering or leaving PWM mode.
if (m == 0b0010) {
// auxMsg[0..3] is frequency, [4..7] is resolution.
uint32_t freq = *reinterpret_cast<uint32_t *>(auxMsg);
uint8_t res = *reinterpret_cast<uint8_t *>(auxMsg + 4);
// Fallback to defaults.
if (freq == 0) freq = 1000;
if (res == 0) res = esp32AnalogWRes;
ledcAttach(p, freq, res);
return;
} else {
ledcDetach(p);
}
#endif

// On the SAMD21 and RA4M1, mode needs to be INPUT when using the DAC.
#if defined(__SAMD21G18A__) || defined(_RENESAS_RA_)
if (m == 0b0100){
pinMode(p, INPUT);
return;
}
#endif

// Handle INPUT_* states on boards implementing them.
#ifdef INPUT_PULLDOWN
if (m == 0b0011) {
pinMode(p, INPUT_PULLDOWN);
return;
}
#endif

#ifdef INPUT_PULLUP
if (m == 0b0101) {
pinMode(p, INPUT_PULLUP);
return;
}
#endif

// Handle OUTPUT_* states on boards implementing them.
#ifdef OUTPUT_OPEN_DRAIN
if (m == 0b0110) {
Expand Down Expand Up @@ -100,7 +111,7 @@ void Denko::dWrite(byte p, byte v, boolean echo) {
#endif
ledcDetach(p);
#endif

if (v == 0) {
digitalWrite(p, LOW);
}
Expand All @@ -122,8 +133,6 @@ byte Denko::dRead(byte p) {
// Write an analog output pin. 0 for LOW, up to 255 for HIGH @ 8-bit resolution.
void Denko::pwmWrite(byte p, int v, boolean echo) {
#ifdef ESP32
// Try to reattach LEDC each time. Should be cached, and dWrite might detatch it.
ledcAttach(p, 1000, esp32AnalogWRes);
ledcWrite(p, v);
#else
analogWrite(p,v);
Expand All @@ -145,7 +154,7 @@ void Denko::dacWrite(byte p, int v, boolean echo) {
#if defined(ESP32) && defined(SOC_DAC_SUPPORTED)
::dacWrite(p, v);
#endif

#if defined(__SAM3X8E__) || defined(__SAMD21G18A__) || defined(_RENESAS_RA_)
analogWrite(p, v);
#endif
Expand Down

0 comments on commit 0081907

Please sign in to comment.