Pico-Arduino
PicoPWM.h
1 #pragma once
2 
3 #include "pico/stdlib.h"
4 #include <inttypes.h>
5 #include "hardware/gpio.h"
6 #include "hardware/adc.h"
7 #include "hardware/pwm.h"
8 #include "hardware/clocks.h"
9 #include "PicoPinFunction.h"
10 
11 #ifndef PWM_MAX_NUMER
12 // you can use up to 65535
13 #define PWM_MAX_NUMER 1000
14 #endif
15 
16 #ifndef PWM_READ_REPEAT
17 #define PWM_READ_REPEAT 10
18 #endif
19 
20 namespace pico_arduino {
21 
23 inline uint32_t pico_pwm_wrap_count;
24 inline int pico_pwm_actual_pin;
25 inline static void on_pico_pwm_wrap() {
26  // Clear the interrupt flag that brought us here
27  pwm_clear_irq(pwm_gpio_to_slice_num(pico_pwm_actual_pin));
28  // increment the wrap counter
30 };
31 
33 inline static float frequency(uint64_t period_nano_sec){
34  return 1000000000.0 / period_nano_sec;
35 }
36 
37 
47 class PicoPWMWriter : public PinSetup {
48  public:
50  PicoPWMWriter(uint64_t periodNanoSeconds){
51  Logger.debug("PicoPWMWriter");
52  period_nano_sec = periodNanoSeconds;
53  tick_period_nano_sec = period_nano_sec / PWM_MAX_NUMER;
54  }
55 
57  void begin(pin_size_t pinNumber, uint64_t initialDutyCyleNanoSeconds=0){
58  Logger.printf(PicoLogger::Debug, "PicoPWMWriter::begin %d\n", pinNumber);
59  initial_duty_cycle = initialDutyCyleNanoSeconds;
60  // this is executed only once per instance
61  setupConfig();
62  // this is executed only once per pin
63  pinFunction.usePin(pinNumber, PIN_FUNC_PWM, this);
64  }
65 
67  virtual void setupPin(PinInfo *info, pin_size_t pinNumber) {
68  Logger.debug("PicoPWMWriter::setupPin");
69  // determine the slice number and channel
70  uint8_t slice_num = pwm_gpio_to_slice_num(pinNumber);
71 
72  // start only in output mode
73  Logger.debug("pwm_init for OUTPUT");
74  pwm_init(slice_num, &config, true);
75  setDutyCycle(pinNumber, initial_duty_cycle);
76 
77  Logger.debug("gpio_set_function");
78  gpio_set_function(pinNumber, GPIO_FUNC_PWM);
79  }
80 
82  void end(pin_size_t pin){
83  Logger.debug("PicoPWMWriter::end");
84 
85  setDutyCycle(pin, 0);
86  pinFunction.clear(pin);
87  }
88 
90  void setDutyCycle(pin_size_t pin, uint64_t dutyCyleNanoSeconds){
91  Logger.debug("PicoPWMWriter::setDutyCycle");
92  uint slice_num = pwm_gpio_to_slice_num(pin);
93  uint channel = pwm_gpio_to_channel(pin);
94  uint16_t value = (1.0 * dutyCyleNanoSeconds) / tick_period_nano_sec;
95 
96  if (Logger.isLogging(PicoLogger::Debug)){
97  char str[40];
98  sprintf(str,"%lu", dutyCyleNanoSeconds);
99  Logger.debug("PWM duty cycle ns:",str);
100  sprintf(str,"%lu", value);
101  Logger.debug("PWM duty cycle(internal):",str);
102  }
103  pwm_set_chan_level(slice_num, channel, value);
104  }
105 
107  float frequency(){
108  return pico_arduino::frequency(period_nano_sec);
109  }
110 
111  // frequency of a single counter tick
112  float frequencyTick(){
113  return pico_arduino::frequency(tick_period_nano_sec);
114  }
115 
116  // period of a single tick in nanoseconds
117  float periodTick(){
118  return tick_period_nano_sec;
119  }
120 
122  uint64_t period() {
123  return period_nano_sec;
124  }
125 
126  protected:
127  PicoPinFunction pinFunction = PicoPinFunction::instance();
128  PinMode pin_mode;
129  pwm_config config;
130  uint64_t period_nano_sec;
131  uint64_t initial_duty_cycle;
132  float tick_period_nano_sec; // values must be <= 65535
133  bool is_config_done = false;
134 
136  bool setupConfig(){
137  if (!is_config_done) {
138  Logger.debug("PicoPWMWriter::setupConfig");
139  is_config_done = true;
140  uint16_t wrap = PWM_MAX_NUMER ;
141  config = pwm_get_default_config();
142  // this should actually be identical with PWM_MAX_NUMER
143  uint32_t sys_clock_freq = clock_get_hz(clk_sys);
144  // divider to achieve the requested periodNanoSeconds
145  float dividerTick = (1.0 * sys_clock_freq) / frequencyTick();
146  logConfig(sys_clock_freq, dividerTick, wrap);
147 
148  // Set divider, reduces counter clock to sysclock/this value
149  pwm_config_set_clkdiv(&config, dividerTick);
150  pwm_config_set_wrap(&config, wrap);
151 
152  return true;
153  }
154  return false;
155  }
156 
157  void logConfig(uint32_t sys_clock_freq, float dividerTick, uint16_t wrap ) {
158  if (Logger.isLogging(PicoLogger::Debug)){
159  char str[80];
160  sprintf(str,"%lu", period_nano_sec);
161  Logger.debug("Period ns:", str);
162  sprintf(str,"%f", tick_period_nano_sec);
163  Logger.debug("Tick period ns:", str);
164  sprintf(str,"%f", frequency());
165  Logger.debug("PWM hz:", str);
166  sprintf(str,"%f", sys_clock_freq);
167  Logger.debug("Systemclock hz:", str);
168  sprintf(str,"%f", dividerTick);
169  Logger.debug("Tick divider:", str);
170  sprintf(str,"%d", wrap);
171  Logger.debug("PWM wrap:", str);
172  }
173  }
174 
175 
176 };
177 
182 class PicoPWMReader : public PinSetup {
183  public:
190  PicoPWMReader(uint64_t periodNanoSeconds){
191  Logger.debug("PicoPWMReader");
192  period_nano_sec = periodNanoSeconds;
193  tick_period_nano_sec = period_nano_sec / PWM_MAX_NUMER;
194  }
195 
197  void begin(pin_size_t pinNumber){
198  Logger.printf(PicoLogger::Info, "PicoPWMReader::begin %d\n", pinNumber);
199 
200  // this is executed only once per instance
201  setupConfig();
202 
203  // force input mode
204  pinFunction.setPinMode(pinNumber, INPUT);
205  pin_mode = pinFunction.pinMode(pinNumber);
206 
207  // this is executed only once per pin
208  pinFunction.usePin(pinNumber, PIN_FUNC_PWM, this);
209  }
210 
212  virtual void setupPin(PinInfo *info, pin_size_t pinNumber) {
213  Logger.debug("PicoPWMReader::setupPin");
214  // determine the slice number and channel
215  uint8_t slice_num = pwm_gpio_to_slice_num(pinNumber);
216 
217  // start only in output mode
218  Logger.debug("pwm_init for INPUT");
219  // READ Mode -> count high !
220  pwm_config_set_clkdiv_mode(&config, PWM_DIV_B_HIGH);
221  pwm_init(slice_num, &config, false);
222 
223  Logger.debug("gpio_set_function");
224  gpio_set_function(pinNumber, GPIO_FUNC_PWM);
225  }
226 
228  void end(pin_size_t pin){
229  Logger.debug("PicoPWMReader::end");
230  pinFunction.clear(pin);
231  }
232 
234  uint64_t measureDutyCycle(uint gpio) {
235  return measureDutyCyclePercent(gpio) * period() / 100.0 ;
236  }
237 
239  float measureDutyCyclePercent(uint gpio) {
240  Logger.debug("PicoPWMReader::measureDutyCycle");
241  float result = 0;
242  if (pin_mode==INPUT) {
243  // Only the PWM B pins can be used as inputs.
244  if(pwm_gpio_to_channel(gpio) == PWM_CHAN_B) {
245  // used by interrupt handler
246  pico_pwm_actual_pin = gpio;
247  uint slice_num = pwm_gpio_to_slice_num(gpio);
248 
249  // Mask our slice's IRQ output into the PWM block's single interrupt line,
250  // and register our interrupt handler
251  pwm_clear_irq(slice_num);
252  pwm_set_irq_enabled(slice_num, true);
253  irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pico_pwm_wrap);
254  irq_set_enabled(PWM_IRQ_WRAP, true);
255 
256  // measure input pin
257  uint64_t counter = count(slice_num, PWM_READ_REPEAT);
258 
259  // clean up irq
260  pwm_clear_irq(slice_num);
261  pwm_set_irq_enabled(slice_num, false);
262  irq_remove_handler (PWM_IRQ_WRAP, on_pico_pwm_wrap);
263 
264  // convert to percent
265  result = 100.0 * counter / max_ticks_per_period;
266  } else {
267  Logger.error("measureDutyCycle() only allowed the PWM B pins");
268  }
269  } else {
270  Logger.error("measureDutyCycle() only allowed with pinMode INPUT");
271  }
272  return result;
273  }
274 
276  uint64_t period() {
277  return period_nano_sec;
278  }
279 
280 
281  protected:
282  PicoPinFunction pinFunction = PicoPinFunction::instance();
283  PinMode pin_mode;
284  pwm_config config;
285  uint64_t period_nano_sec;
286  uint64_t max_ticks_per_period;
287  float tick_period_nano_sec; // values must be <= 65535
288  bool is_config_done = false;
289 
290 
292  bool setupConfig(){
293  if (!is_config_done) {
294  Logger.debug("PicoPWMReader::setupConfig");
295  is_config_done = true;
296  uint16_t wrap = PWM_MAX_NUMER ;
297  config = pwm_get_default_config();
298  // this should actually be identical with PWM_MAX_NUMER
299  uint32_t sys_clock_freq = clock_get_hz(clk_sys);
300 
301  // calculate max ticks per period -> sys_clock_freq
302  max_ticks_per_period = period_nano_sec * sys_clock_freq / 1000000000;
303 
304  // Set divider, run at full speed
305  pwm_config_set_clkdiv(&config, 1.0);
306  pwm_config_set_wrap(&config, wrap);
307 
308  logConfig(sys_clock_freq, wrap);
309  return true;
310  }
311  return false;
312  }
313 
314  void logConfig(uint32_t sys_clock_freq, uint16_t wrap ) {
315  if (Logger.isLogging(PicoLogger::Debug)){
316  char str[80];
317  sprintf(str,"%lu", period_nano_sec);
318  Logger.debug("Period ns:", str);
319  sprintf(str,"%f", tick_period_nano_sec);
320  Logger.debug("Tick period ns:", str);
321  sprintf(str,"%f", sys_clock_freq);
322  Logger.debug("Systemclock hz:", str);
323  sprintf(str,"%d", wrap);
324  Logger.debug("PWM wrap:", str);
325  }
326  }
327 
328  uint64_t count(uint slice_num, int repeat) {
329  uint64_t sleep_period_us = (period_nano_sec / 1000 ); // 2040
331 
332  if (Logger.isLogging(PicoLogger::Debug)){
333  char sleep_str[70];
334  sprintf(sleep_str,"sleep_us: %lu", sleep_period_us);
335  Logger.debug(sleep_str);
336  }
337 
338  uint64_t counter = 0;
339  for (int j=0;j<repeat;j++) {
340  // measure n pwm cycles
341  pwm_set_counter (slice_num, 0);
342  pwm_set_enabled(slice_num, true);
343  sleep_us((sleep_period_us )); // sleep microsendonds
344  pwm_set_enabled(slice_num, false);
345 
346  counter += pwm_get_counter(slice_num);
347  }
348  counter += (pico_pwm_wrap_count * PWM_MAX_NUMER);
349  return counter / repeat;
350  }
351 
352 };
353 
362 class PicoPWMNano {
363  public:
365  PicoPWMNano(uint64_t periodNanoSeconds) {
366  Logger.debug("PicoPWMNano");
367  writer = new PicoPWMWriter(periodNanoSeconds);
368  reader = new PicoPWMReader(periodNanoSeconds);
369  }
370 
371  // Destrucotor - clean up
372  ~PicoPWMNano(){
373  delete writer;
374  delete reader;
375  }
376 
378  void begin(pin_size_t gpio, uint64_t initialDutyCyleNanoSeconds=0, PinMode pinMode=OUTPUT){
379  // determine PinMode from PinFunction - otherwise we use the indicated PinMode
380  if (!pin_function.isModeDefined(gpio)){
381  pin_function.setPinMode(gpio, pinMode);
382  }
383 
384  if (pin_function.isInput(gpio)){
385  reader->begin(gpio);
386  } else if (pin_function.isOutput(gpio)){
387  writer->begin(gpio, initialDutyCyleNanoSeconds);
388  }
389  }
390 
392  void end(pin_size_t gpio){
393  writer->end(gpio);
394  reader->end(gpio);
395  }
396 
398  void setDutyCycle(pin_size_t gpio, uint64_t dutyCyleNanoSeconds){
399  if (isOutput(gpio)){
400  writer-> setDutyCycle(gpio, dutyCyleNanoSeconds);
401  }
402  }
403 
405  uint64_t measureDutyCycle(uint gpio) {
406  uint64_t result = 0;
407  if (isInput(gpio)) {
408  result = reader->measureDutyCycle(gpio);
409  }
410  return result;
411  }
412 
414  float measureDutyCyclePercent(uint gpio) {
415  float result = 0;
416  if (isInput(gpio)) {
417  result = reader->measureDutyCyclePercent(gpio);
418  }
419  return result;
420  }
421 
423  float frequency(){
424  return writer->frequency();
425  }
426 
428  uint64_t period() {
429  return writer->period();
430  }
431 
432  protected:
433  PicoPinFunction pin_function = PicoPinFunction::instance();
434  PinMode pin_mode;
435  PicoPWMWriter *writer;
436  PicoPWMReader *reader;
437 
438  bool isOutput(pin_size_t gpio) {
439  return pin_function.isOutput(gpio);
440  }
441  bool isInput(pin_size_t gpio) {
442  return pin_function.isInput(gpio);
443  }
444 
445 };
446 
447 
453 class PicoPWM {
454  public:
456  PicoPWM(uint64_t frequency, uint64_t maxValue){
457  // convert frequency to period
458  period_nano_sec = 1000000000l / frequency;
459  nano = new PicoPWMNano(period_nano_sec);
460  max_value = maxValue;
461  }
462 
465  delete nano;
466  }
467 
469  void begin(pin_size_t pin, uint64_t initalValue){
470  nano->begin(pin, initalValue);
471  }
472 
474  void begin(pin_size_t pin, PinMode pinMode=OUTPUT){
475  nano->begin(pin, 0, pinMode);
476  }
477 
479  void end(pin_size_t pin){
480  nano->end(pin);
481  }
482 
484  void write(pin_size_t pin, uint64_t value){
485  nano->setDutyCycle(pin, valueToDutyCycle(value));
486  }
487 
489  uint64_t read(pin_size_t pin){
490  return dutyCycleToValue(nano->measureDutyCycle(pin));
491  }
492 
494  float readPercent(pin_size_t pin){
495  return 100.0 * read(pin) / max_value;
496  }
497 
499  uint64_t period() {
500  return nano->period();
501  }
502 
504  uint64_t frequency(){
505  return nano->frequency();
506  }
507 
508  protected:
509  PicoPWMNano *nano;
510  int64_t max_value;
511  uint64_t period_nano_sec;
512 
514  uint64_t valueToDutyCycle(uint64_t value){
515  return map(value, 0, max_value, 0, period_nano_sec);
516  }
517 
518  uint64_t dutyCycleToValue(uint64_t dutyCycle){
519  return map(dutyCycle, 0, period_nano_sec, 0, max_value) + 1;
520  }
521 
522 };
523 
524 }
525 
526 
virtual int printf(LogLevel current_level, const char *fmt,...)
printf support
Definition: PicoLogger.h:65
virtual bool isLogging(LogLevel level=Info)
checks if the logging is active
Definition: PicoLogger.h:40
virtual void error(const char *str, const char *str1=nullptr, const char *str2=nullptr)
logs an error
Definition: PicoLogger.h:45
virtual void debug(const char *str, const char *str1=nullptr, const char *str2=nullptr)
writes an debug message
Definition: PicoLogger.h:60
This is an even more powerfull PWM API where we can specify a user defined input range and the cycle ...
Definition: PicoPWM.h:453
uint64_t frequency()
Provides the frequncy in hz which was specified in the constructor.
Definition: PicoPWM.h:504
void begin(pin_size_t pin, uint64_t initalValue)
setup a pin for pwm write
Definition: PicoPWM.h:469
void write(pin_size_t pin, uint64_t value)
Defines the active period in the value range from 0 to maxValue.
Definition: PicoPWM.h:484
void begin(pin_size_t pin, PinMode pinMode=OUTPUT)
setup a pin for pwm read or write
Definition: PicoPWM.h:474
PicoPWM(uint64_t frequency, uint64_t maxValue)
Default constructor.
Definition: PicoPWM.h:456
void end(pin_size_t pin)
sets the pin to low
Definition: PicoPWM.h:479
uint64_t period()
Provides the full cycle period in nanoseconds.
Definition: PicoPWM.h:499
float readPercent(pin_size_t pin)
Provides the duty cyle in percent.
Definition: PicoPWM.h:494
uint64_t read(pin_size_t pin)
Reads the active period in the value range from 0 to maxValue.
Definition: PicoPWM.h:489
~PicoPWM()
Destructor.
Definition: PicoPWM.h:464
uint64_t valueToDutyCycle(uint64_t value)
converts an input value to the duty cycle in nanosec
Definition: PicoPWM.h:514
Basic PWM API based on the input and output in nano seconds The Raspberry Pico has 8 controllable PWM...
Definition: PicoPWM.h:362
float frequency()
converts the PWM period to hz for the PWM output
Definition: PicoPWM.h:423
void begin(pin_size_t gpio, uint64_t initialDutyCyleNanoSeconds=0, PinMode pinMode=OUTPUT)
setup the pin mode only if necessary
Definition: PicoPWM.h:378
uint64_t measureDutyCycle(uint gpio)
measures the duty cycle in nanoseconds - only the PWM B pins can be used as inputs!
Definition: PicoPWM.h:405
uint64_t period()
provides the full cycle period in nanoseconds
Definition: PicoPWM.h:428
PicoPWMNano(uint64_t periodNanoSeconds)
Constructor: Defines the length of a full cycle in nanoseconds (mio of seconds)
Definition: PicoPWM.h:365
void end(pin_size_t gpio)
sets the output pins to low
Definition: PicoPWM.h:392
float measureDutyCyclePercent(uint gpio)
provides the duty cycle in percent
Definition: PicoPWM.h:414
void setDutyCycle(pin_size_t gpio, uint64_t dutyCyleNanoSeconds)
Defines the active period is nanoseconds.
Definition: PicoPWM.h:398
PWM class which supports the input of PWM signals.
Definition: PicoPWM.h:182
uint64_t measureDutyCycle(uint gpio)
measures the duty cycle (active period) in nanoseconds - only the PWM B pins can be used as inputs!
Definition: PicoPWM.h:234
uint64_t period()
provides the full cycle period in nanoseconds
Definition: PicoPWM.h:276
void begin(pin_size_t pinNumber)
setup a pin for pwm write
Definition: PicoPWM.h:197
PicoPWMReader(uint64_t periodNanoSeconds)
Constructor: Defines the length of a full cycle in nanoseconds (mio of seconds). This is used as mesu...
Definition: PicoPWM.h:190
void end(pin_size_t pin)
sets the pin to low
Definition: PicoPWM.h:228
virtual void setupPin(PinInfo *info, pin_size_t pinNumber)
setup of pin for PWM Input
Definition: PicoPWM.h:212
bool setupConfig()
provides the configuration - returns true if we have a new configuration
Definition: PicoPWM.h:292
float measureDutyCyclePercent(uint gpio)
provides the duty cycle in percent
Definition: PicoPWM.h:239
Support for the generation of PWM signals. For the standard Arduino functionality we use on fixed fre...
Definition: PicoPWM.h:47
float frequency()
converts the PWM period to hz
Definition: PicoPWM.h:107
void setDutyCycle(pin_size_t pin, uint64_t dutyCyleNanoSeconds)
Defines the active period is nanoseconds.
Definition: PicoPWM.h:90
virtual void setupPin(PinInfo *info, pin_size_t pinNumber)
setup of pin for PWM Output
Definition: PicoPWM.h:67
PicoPWMWriter(uint64_t periodNanoSeconds)
Constructor: Defines the length of a full cycle in nanoseconds (mio of seconds)
Definition: PicoPWM.h:50
uint64_t period()
provides the full cycle period in nanoseconds
Definition: PicoPWM.h:122
bool setupConfig()
provides the configuration - returns true if we have a new configuration
Definition: PicoPWM.h:136
void end(pin_size_t pin)
sets the pin to low
Definition: PicoPWM.h:82
void begin(pin_size_t pinNumber, uint64_t initialDutyCyleNanoSeconds=0)
setup a pin for pwm write
Definition: PicoPWM.h:57
The pico requires that the function of the pin is defined. In Arduino, there is no such concept - how...
Definition: PicoPinFunction.h:133
PinMode pinMode(pin_size_t pinNumber)
returns the pin mode
Definition: PicoPinFunction.h:184
void clear(pin_size_t pinNumber)
set gpio function to GPIO_FUNC_NULL
Definition: PicoPinFunction.h:198
void usePin(pin_size_t pinNumber, PinFunctionEnum pinFunction, PinSetup *setup=nullptr)
setup Pico pin init function bysed on functionality
Definition: PicoPinFunction.h:205
bool isModeDefined(pin_size_t pinNumber)
checks if the mode was defined for the pin
Definition: PicoPinFunction.h:159
bool isInput(pin_size_t pinNumber)
checks if the pin has been defined as input
Definition: PicoPinFunction.h:164
bool setPinMode(pin_size_t pinNumber, PinMode pinMode)
defines the actual Arduino PinMode. Returns true if it needed to be changed
Definition: PicoPinFunction.h:144
bool isOutput(pin_size_t pinNumber)
checks if the pin has been defined as output
Definition: PicoPinFunction.h:169
Base class for function specific pin setup and pin use functionality.
Definition: PicoPinFunction.h:40
Pico Arduino Framework.
Definition: Arduino.cpp:26
uint32_t pico_pwm_wrap_count
For measureDutyCycle we need to track the pwm wrap interrupts.
Definition: PicoPWM.h:23
Information about an the status and the Arduino PinMode of an individual pin.
Definition: PicoPinFunction.h:28