* use valid_params_if macro for lock assertion * fixup param checking in pwm.h * Fix range of fractional divider parameter check in pwm.h Co-authored-by: Luke Wren <wren6991@gmail.com>
521 lines
18 KiB
C
521 lines
18 KiB
C
/*
|
|
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#ifndef _HARDWARE_PWM_H
|
|
#define _HARDWARE_PWM_H
|
|
|
|
#include "pico.h"
|
|
#include "hardware/structs/pwm.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PWM, Enable/disable assertions in the PWM module, type=bool, default=0, group=hadrware_pwm
|
|
#ifndef PARAM_ASSERTIONS_ENABLED_PWM
|
|
#define PARAM_ASSERTIONS_ENABLED_PWM 0
|
|
#endif
|
|
|
|
/** \file hardware/pwm.h
|
|
* \defgroup hardware_pwm hardware_pwm
|
|
*
|
|
* Hardware Pulse Width Modulation (PWM) API
|
|
*
|
|
* The RP2040 PWM block has 8 identical slices. Each slice can drive two PWM output signals, or
|
|
* measure the frequency or duty cycle of an input signal. This gives a total of up to 16 controllable
|
|
* PWM outputs. All 30 GPIOs can be driven by the PWM block
|
|
*
|
|
* The PWM hardware functions by continuously comparing the input value to a free-running counter. This produces a
|
|
* toggling output where the amount of time spent at the high output level is proportional to the input value. The fraction of
|
|
* time spent at the high signal level is known as the duty cycle of the signal.
|
|
*
|
|
* The default behaviour of a PWM slice is to count upward until the wrap value (\ref pwm_config_set_wrap) is reached, and then
|
|
* immediately wrap to 0. PWM slices also offer a phase-correct mode, where the counter starts to count downward after
|
|
* reaching TOP, until it reaches 0 again.
|
|
*
|
|
* \subsection pwm_example Example
|
|
* \addtogroup hardware_pwm
|
|
* \include hello_pwm.c
|
|
*/
|
|
|
|
/** \brief PWM Divider mode settings
|
|
* \ingroup hardware_pwm
|
|
*
|
|
*/
|
|
enum pwm_clkdiv_mode
|
|
{
|
|
PWM_DIV_FREE_RUNNING, ///< Free-running counting at rate dictated by fractional divider
|
|
PWM_DIV_B_HIGH, ///< Fractional divider is gated by the PWM B pin
|
|
PWM_DIV_B_RISING, ///< Fractional divider advances with each rising edge of the PWM B pin
|
|
PWM_DIV_B_FALLING ///< Fractional divider advances with each falling edge of the PWM B pin
|
|
};
|
|
|
|
enum pwm_chan
|
|
{
|
|
PWM_CHAN_A = 0,
|
|
PWM_CHAN_B = 1
|
|
};
|
|
|
|
typedef struct {
|
|
uint32_t csr;
|
|
uint32_t div;
|
|
uint32_t top;
|
|
} pwm_config;
|
|
|
|
static inline void check_slice_num_param(__unused uint slice_num) {
|
|
valid_params_if(PWM, slice_num < NUM_PWM_SLICES);
|
|
}
|
|
|
|
/** \brief Determine the PWM slice that is attached to the specified GPIO
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \return The PWM slice number that controls the specified GPIO.
|
|
*/
|
|
static inline uint pwm_gpio_to_slice_num(uint gpio) {
|
|
valid_params_if(PWM, gpio < NUM_BANK0_GPIOS);
|
|
return (gpio >> 1u) & 7u;
|
|
}
|
|
|
|
/** \brief Determine the PWM channel that is attached to the specified GPIO.
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Each slice 0 to 7 has two channels, A and B.
|
|
*
|
|
* \return The PWM channel that controls the specified GPIO.
|
|
*/
|
|
static inline uint pwm_gpio_to_channel(uint gpio) {
|
|
valid_params_if(PWM, gpio < NUM_BANK0_GPIOS);
|
|
return gpio & 1u;
|
|
}
|
|
|
|
/** \brief Set phase correction in a PWM configuration
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param c PWM configuration struct to modify
|
|
* \param phase_correct true to set phase correct modulation, false to set trailing edge
|
|
*
|
|
* Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached,
|
|
* the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled.
|
|
*/
|
|
static inline void pwm_config_set_phase_correct(pwm_config *c, bool phase_correct) {
|
|
c->csr = (c->csr & ~PWM_CH0_CSR_PH_CORRECT_BITS)
|
|
| (!!phase_correct << PWM_CH0_CSR_PH_CORRECT_LSB);
|
|
}
|
|
|
|
/** \brief Set clock divider in a PWM configuration
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param c PWM configuration struct to modify
|
|
* \param div Value to divide counting rate by. Must be greater than or equal to 1.
|
|
*
|
|
* If the divide mode is free-running, the PWM counter runs at clk_sys / div.
|
|
* Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge)
|
|
* before passing them on to the PWM counter.
|
|
*/
|
|
static inline void pwm_config_set_clkdiv(pwm_config *c, float div) {
|
|
c->div = (uint32_t)(div * (float)(1u << PWM_CH1_DIV_INT_LSB));
|
|
}
|
|
|
|
/** \brief Set PWM clock divider in a PWM configuration
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param c PWM configuration struct to modify
|
|
* \param div integer value to reduce counting rate by. Must be greater than or equal to 1.
|
|
*
|
|
* If the divide mode is free-running, the PWM counter runs at clk_sys / div.
|
|
* Otherwise, the divider reduces the rate of events seen on the B pin input (level or edge)
|
|
* before passing them on to the PWM counter.
|
|
*/
|
|
static inline void pwm_config_set_clkdiv_int(pwm_config *c, uint div) {
|
|
c->div = div << PWM_CH1_DIV_INT_LSB;
|
|
}
|
|
|
|
/** \brief Set PWM counting mode in a PWM configuration
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param c PWM configuration struct to modify
|
|
* \param mode PWM divide/count mode
|
|
*
|
|
* Configure which event gates the operation of the fractional divider.
|
|
* The default is always-on (free-running PWM). Can also be configured to count on
|
|
* high level, rising edge or falling edge of the B pin input.
|
|
*/
|
|
static inline void pwm_config_set_clkdiv_mode(pwm_config *c, enum pwm_clkdiv_mode mode) {
|
|
valid_params_if(PWM, mode >= PWM_DIV_FREE_RUNNING && mode <= PWM_DIV_B_FALLING);
|
|
c->csr = (c->csr & ~PWM_CH0_CSR_DIVMODE_BITS)
|
|
| (mode << PWM_CH0_CSR_DIVMODE_LSB);
|
|
}
|
|
|
|
/** \brief Set output polarity in a PWM configuration
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param c PWM configuration struct to modify
|
|
* \param a true to invert output A
|
|
* \param b true to invert output B
|
|
*/
|
|
static inline void pwm_config_set_output_polarity(pwm_config *c, bool a, bool b) {
|
|
c->csr = (c->csr & ~(PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS))
|
|
| ((!!a << PWM_CH0_CSR_A_INV_LSB) | (!!b << PWM_CH0_CSR_B_INV_LSB));
|
|
}
|
|
|
|
/** \brief Set PWM counter wrap value in a PWM configuration
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Set the highest value the counter will reach before returning to 0. Also known as TOP.
|
|
*
|
|
* \param c PWM configuration struct to modify
|
|
* \param wrap Value to set wrap to
|
|
*/
|
|
static inline void pwm_config_set_wrap(pwm_config *c, uint16_t wrap) {
|
|
c->top = wrap;
|
|
}
|
|
|
|
/** \brief Initialise a PWM with settings from a configuration object
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Use the \ref pwm_get_default_config() function to initialise a config structure, make changes as
|
|
* needed using the pwm_config_* functions, then call this function to set up the PWM.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param c The configuration to use
|
|
* \param start If true the PWM will be started running once configured. If false you will need to start
|
|
* manually using \ref pwm_set_enabled() or \ref pwm_set_mask_enabled()
|
|
*/
|
|
static inline void pwm_init(uint slice_num, pwm_config *c, bool start) {
|
|
check_slice_num_param(slice_num);
|
|
pwm_hw->slice[slice_num].csr = 0;
|
|
|
|
pwm_hw->slice[slice_num].ctr = PWM_CH0_CTR_RESET;
|
|
pwm_hw->slice[slice_num].cc = PWM_CH0_CC_RESET;
|
|
pwm_hw->slice[slice_num].top = c->top;
|
|
pwm_hw->slice[slice_num].div = c->div;
|
|
pwm_hw->slice[slice_num].csr = c->csr | (!!start << PWM_CH0_CSR_EN_LSB);
|
|
}
|
|
|
|
/** \brief Get a set of default values for PWM configuration
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* PWM config is free running at system clock speed, no phase correction, wrapping at 0xffff,
|
|
* with standard polarities for channels A and B.
|
|
*
|
|
* \return Set of default values.
|
|
*/
|
|
static inline pwm_config pwm_get_default_config(void) {
|
|
pwm_config c = {0, 0, 0};
|
|
pwm_config_set_phase_correct(&c, false);
|
|
pwm_config_set_clkdiv_int(&c, 1);
|
|
pwm_config_set_clkdiv_mode(&c, PWM_DIV_FREE_RUNNING);
|
|
pwm_config_set_output_polarity(&c, false, false);
|
|
pwm_config_set_wrap(&c, 0xffffu);
|
|
return c;
|
|
}
|
|
|
|
/** \brief Set the current PWM counter wrap value
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Set the highest value the counter will reach before returning to 0. Also
|
|
* known as TOP.
|
|
*
|
|
* The counter wrap value is double-buffered in hardware. This means that,
|
|
* when the PWM is running, a write to the counter wrap value does not take
|
|
* effect until after the next time the PWM slice wraps (or, in phase-correct
|
|
* mode, the next time the slice reaches 0). If the PWM is not running, the
|
|
* write is latched in immediately.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param wrap Value to set wrap to
|
|
*/
|
|
static inline void pwm_set_wrap(uint slice_num, uint16_t wrap) {
|
|
check_slice_num_param(slice_num);
|
|
pwm_hw->slice[slice_num].top = wrap;
|
|
}
|
|
|
|
/** \brief Set the current PWM counter compare value for one channel
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Set the value of the PWM counter compare value, for either channel A or channel B
|
|
*
|
|
* The counter compare register is double-buffered in hardware. This means
|
|
* that, when the PWM is running, a write to the counter compare values does
|
|
* not take effect until the next time the PWM slice wraps (or, in
|
|
* phase-correct mode, the next time the slice reaches 0). If the PWM is not
|
|
* running, the write is latched in immediately.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param chan Which channel to update. 0 for A, 1 for B.
|
|
* \param level new level for the selected output
|
|
*/
|
|
static inline void pwm_set_chan_level(uint slice_num, uint chan, uint16_t level) {
|
|
check_slice_num_param(slice_num);
|
|
hw_write_masked(
|
|
&pwm_hw->slice[slice_num].cc,
|
|
level << (chan ? PWM_CH0_CC_B_LSB : PWM_CH0_CC_A_LSB),
|
|
chan ? PWM_CH0_CC_B_BITS : PWM_CH0_CC_A_BITS
|
|
);
|
|
}
|
|
|
|
/** \brief Set PWM counter compare values
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Set the value of the PWM counter compare values, A and B
|
|
*
|
|
* The counter compare register is double-buffered in hardware. This means
|
|
* that, when the PWM is running, a write to the counter compare values does
|
|
* not take effect until the next time the PWM slice wraps (or, in
|
|
* phase-correct mode, the next time the slice reaches 0). If the PWM is not
|
|
* running, the write is latched in immediately.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param level_a Value to set compare A to. When the counter reaches this value the A output is deasserted
|
|
* \param level_b Value to set compare B to. When the counter reaches this value the B output is deasserted
|
|
*/
|
|
static inline void pwm_set_both_levels(uint slice_num, uint16_t level_a, uint16_t level_b) {
|
|
check_slice_num_param(slice_num);
|
|
pwm_hw->slice[slice_num].cc = (level_b << PWM_CH0_CC_B_LSB) | (level_a << PWM_CH0_CC_A_LSB);
|
|
}
|
|
|
|
/** \brief Helper function to set the PWM level for the slice and channel associated with a GPIO.
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Look up the correct slice (0 to 7) and channel (A or B) for a given GPIO, and update the corresponding
|
|
* counter-compare field.
|
|
*
|
|
* This PWM slice should already have been configured and set running. Also be careful of multiple GPIOs
|
|
* mapping to the same slice and channel (if GPIOs have a difference of 16).
|
|
*
|
|
* The counter compare register is double-buffered in hardware. This means
|
|
* that, when the PWM is running, a write to the counter compare values does
|
|
* not take effect until the next time the PWM slice wraps (or, in
|
|
* phase-correct mode, the next time the slice reaches 0). If the PWM is not
|
|
* running, the write is latched in immediately.
|
|
*
|
|
* \param gpio GPIO to set level of
|
|
* \param level PWM level for this GPIO
|
|
*/
|
|
static inline void pwm_set_gpio_level(uint gpio, uint16_t level) {
|
|
valid_params_if(PWM, gpio < NUM_BANK0_GPIOS);
|
|
pwm_set_chan_level(pwm_gpio_to_slice_num(gpio), pwm_gpio_to_channel(gpio), level);
|
|
}
|
|
|
|
/** \brief Get PWM counter
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Get current value of PWM counter
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \return Current value of PWM counter
|
|
*/
|
|
static inline uint16_t pwm_get_counter(uint slice_num) {
|
|
check_slice_num_param(slice_num);
|
|
return (pwm_hw->slice[slice_num].ctr);
|
|
}
|
|
|
|
/** \brief Set PWM counter
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Set the value of the PWM counter
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param c Value to set the PWM counter to
|
|
*
|
|
*/
|
|
static inline void pwm_set_counter(uint slice_num, uint16_t c) {
|
|
check_slice_num_param(slice_num);
|
|
pwm_hw->slice[slice_num].ctr = c;
|
|
}
|
|
|
|
/** \brief Advance PWM count
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Advance the phase of a running the counter by 1 count.
|
|
*
|
|
* This function will return once the increment is complete.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
*/
|
|
static inline void pwm_advance_count(uint slice_num) {
|
|
check_slice_num_param(slice_num);
|
|
hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_ADV_BITS);
|
|
while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_ADV_BITS) {
|
|
tight_loop_contents();
|
|
}
|
|
}
|
|
|
|
/** \brief Retard PWM count
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Retard the phase of a running counter by 1 count
|
|
*
|
|
* This function will return once the retardation is complete.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
*/
|
|
static inline void pwm_retard_count(uint slice_num) {
|
|
check_slice_num_param(slice_num);
|
|
hw_set_bits(&pwm_hw->slice[slice_num].csr, PWM_CH0_CSR_PH_RET_BITS);
|
|
while (pwm_hw->slice[slice_num].csr & PWM_CH0_CSR_PH_RET_BITS) {
|
|
tight_loop_contents();
|
|
}
|
|
}
|
|
|
|
/** \brief Set PWM clock divider using an 8:4 fractional value
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param integer 8 bit integer part of the clock divider
|
|
* \param fract 4 bit fractional part of the clock divider
|
|
*/
|
|
static inline void pwm_set_clkdiv_int_frac(uint slice_num, uint8_t integer, uint8_t fract) {
|
|
check_slice_num_param(slice_num);
|
|
valid_params_if(PWM, fract < 16);
|
|
pwm_hw->slice[slice_num].div = (integer << PWM_CH0_DIV_INT_LSB) | (fract << PWM_CH0_DIV_FRAC_LSB);
|
|
}
|
|
|
|
/** \brief Set PWM clock divider
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Set the clock divider. Counter increment will be on sysclock divided by this value, taking in to account the gating.
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param divider Floating point clock divider, 1.f <= value < 256.f
|
|
*/
|
|
static inline void pwm_set_clkdiv(uint slice_num, float divider) {
|
|
check_slice_num_param(slice_num);
|
|
valid_params_if(PWM, divider >= 1.f && divider < 256.f);
|
|
uint8_t i = (uint8_t)divider;
|
|
uint8_t f = (uint8_t)((divider - i) * (0x01 << 4));
|
|
pwm_set_clkdiv_int_frac(slice_num, i, f);
|
|
}
|
|
|
|
/** \brief Set PWM output polarity
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param a true to invert output A
|
|
* \param b true to invert output B
|
|
*/
|
|
static inline void pwm_set_output_polarity(uint slice_num, bool a, bool b) {
|
|
check_slice_num_param(slice_num);
|
|
hw_write_masked(&pwm_hw->slice[slice_num].csr, !!a << PWM_CH0_CSR_A_INV_LSB | !!b << PWM_CH0_CSR_B_INV_LSB,
|
|
PWM_CH0_CSR_A_INV_BITS | PWM_CH0_CSR_B_INV_BITS);
|
|
}
|
|
|
|
|
|
/** \brief Set PWM divider mode
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param mode Required divider mode
|
|
*/
|
|
static inline void pwm_set_clkdiv_mode(uint slice_num, enum pwm_clkdiv_mode mode) {
|
|
check_slice_num_param(slice_num);
|
|
valid_params_if(PWM, mode >= PWM_DIV_FREE_RUNNING && mode <= PWM_DIV_B_FALLING);
|
|
hw_write_masked(&pwm_hw->slice[slice_num].csr, mode << PWM_CH0_CSR_DIVMODE_LSB, PWM_CH0_CSR_DIVMODE_BITS);
|
|
}
|
|
|
|
/** \brief Set PWM phase correct on/off
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param phase_correct true to set phase correct modulation, false to set trailing edge
|
|
*
|
|
* Setting phase control to true means that instead of wrapping back to zero when the wrap point is reached,
|
|
* the PWM starts counting back down. The output frequency is halved when phase-correct mode is enabled.
|
|
*/
|
|
static inline void pwm_set_phase_correct(uint slice_num, bool phase_correct) {
|
|
check_slice_num_param(slice_num);
|
|
hw_write_masked(&pwm_hw->slice[slice_num].csr, phase_correct << PWM_CH0_CSR_PH_CORRECT_LSB, PWM_CH0_CSR_PH_CORRECT_BITS);
|
|
}
|
|
|
|
/** \brief Enable/Disable PWM
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param slice_num PWM slice number
|
|
* \param enabled true to enable the specified PWM, false to disable
|
|
*/
|
|
static inline void pwm_set_enabled(uint slice_num, bool enabled) {
|
|
check_slice_num_param(slice_num);
|
|
hw_write_masked(&pwm_hw->slice[slice_num].csr, !!enabled << PWM_CH0_CSR_EN_LSB, PWM_CH0_CSR_EN_BITS);
|
|
}
|
|
|
|
/** \brief Enable/Disable multiple PWM slices simultaneously
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param mask Bitmap of PWMs to enable/disable. Bits 0 to 7 enable slices 0-7 respectively
|
|
*/
|
|
static inline void pwm_set_mask_enabled(uint32_t mask) {
|
|
pwm_hw->en = mask;
|
|
}
|
|
|
|
/*! \brief Enable PWM instance interrupt
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Used to enable a single PWM instance interrupt
|
|
*
|
|
* \param slice_num PWM block to enable/disable
|
|
* \param enabled true to enable, false to disable
|
|
*/
|
|
static inline void pwm_set_irq_enabled(uint slice_num, bool enabled) {
|
|
check_slice_num_param(slice_num);
|
|
if (enabled) {
|
|
hw_set_bits(&pwm_hw->inte, 1u << slice_num);
|
|
} else {
|
|
hw_clear_bits(&pwm_hw->inte, 1u << slice_num);
|
|
}
|
|
}
|
|
|
|
/*! \brief Enable multiple PWM instance interrupts
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* Use this to enable multiple PWM interrupts at once.
|
|
*
|
|
* \param slice_mask Bitmask of all the blocks to enable/disable. Channel 0 = bit 0, channel 1 = bit 1 etc.
|
|
* \param enabled true to enable, false to disable
|
|
*/
|
|
static inline void pwm_set_irq_mask_enabled(uint32_t slice_mask, bool enabled) {
|
|
valid_params_if(PWM, slice_mask < 256);
|
|
if (enabled) {
|
|
hw_set_bits(&pwm_hw->inte, slice_mask);
|
|
} else {
|
|
hw_clear_bits(&pwm_hw->inte, slice_mask);
|
|
}
|
|
}
|
|
|
|
/*! \brief Clear single PWM channel interrupt
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param slice_num PWM slice number
|
|
*/
|
|
static inline void pwm_clear_irq(uint slice_num) {
|
|
pwm_hw->intr = 1u << slice_num;
|
|
}
|
|
|
|
/*! \brief Get PWM interrupt status, raw
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \return Bitmask of all PWM interrupts currently set
|
|
*/
|
|
static inline int32_t pwm_get_irq_status_mask(void) {
|
|
return pwm_hw->ints;
|
|
}
|
|
|
|
/*! \brief Force PWM interrupt
|
|
* \ingroup hardware_pwm
|
|
*
|
|
* \param slice_num PWM slice number
|
|
*/
|
|
static inline void pwm_force_irq(uint slice_num) {
|
|
pwm_hw->intf = 1u << slice_num;
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif
|