PWM sine wave program using GPTimer to give high resolution output
Posted: Tue Jul 25, 2023 8:59 am
This code should give a high resolution sine wave output for driving a low voltage synchronous motor. In due course it will be modified to give the two required out-of-phase signals and adjustment for fine control of motor speed.
It compiles but does not give a sine wave output on pin 25.
Any help debugging it would be greatly appreciated!
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <esp_timer.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gptimer.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/semphr.h"
#include "rom/ets_sys.h"
#define PWM_PIN GPIO_NUM_25
#define SINE_WAVE_FREQUENCY_MIN 39.5 //automatically double as decimal
#define SINE_WAVE_FREQUENCY_MAX 40.5 //ditto
#define SINE_WAVE_FREQUENCY_STEP 0.001
#define PWM_FREQ_TARGET ((SINE_WAVE_FREQUENCY_MIN + SINE_WAVE_FREQUENCY_MAX) / 2.0) //auto double
#define PWM_STEPS 32
const uint16_t sineWaveTable[PWM_STEPS] = {
2048, 2447, 2831, 3185, 3495, 3750, 3939, 4056,
4095, 4056, 3939, 3750, 3495, 3185, 2831, 2447,
2048, 1649, 1265, 911, 601, 346, 157, 40,
0, 40, 157, 346, 601, 911, 1265, 1649
};
typedef struct {
uint32_t dutyCyclePeriod;
} TimerUserData;
TimerUserData timerUserData; // Global instance of TimerUserData
gptimer_handle_t gptimer = NULL; // Declare the gptimer variable
TaskHandle_t pinOffTask; // Task handle for pinOffnotificationFunction
static bool IRAM_ATTR timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
(void)timer;
(void)edata;
TimerUserData* userData = (TimerUserData*)user_ctx;
uint32_t dutyCyclePeriod = userData->dutyCyclePeriod;
(void)dutyCyclePeriod; // Suppress the unused variable warning
BaseType_t higherPriorityTaskWoken = pdFALSE;
vTaskGenericNotifyGiveFromISR(pinOffTask, 0, &higherPriorityTaskWoken); // Notify the pinOffnotificationFunction task to turn off the PWM pin
portYIELD_FROM_ISR(higherPriorityTaskWoken); // Perform any required context switch if xHigherPriorityTaskWoken is set to pdTRUE
return false;
}
void pinOffnotificationFunction(void *pvParameters) {
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for the notification from timer_on_alarm_cb
gpio_set_level(PWM_PIN, 0); // Set PWM pin to LOW
//vTaskDelay(pdMS_TO_TICKS(1)); // Delay for a short period to avoid immediate re-notification
// note! 1ms = 4000ticks!
ets_delay_us(100); //Stalls execution for #uS
}
}
void generateSineWaveTask(void *pvParameters) {
uint32_t pwmStepPeriod;
uint32_t refFreq = 4 * 1000 * 1000; //Hz
uint8_t tableIndex = 0; // Declare and initialize the tableIndex variable
gptimer_alarm_config_t alarm_config = {
.alarm_count = 0,
.reload_count = 0,
.flags.auto_reload_on_alarm = false,
};
gptimer_start(gptimer);
while (1) {
gpio_set_level(PWM_PIN, 1); // Set PWM pin to HIGH
pwmStepPeriod = refFreq / (PWM_FREQ_TARGET * 32); //ticks
uint16_t tableValue = sineWaveTable[tableIndex];
uint32_t dutyCyclePeriod = (tableValue * pwmStepPeriod) / 65535; // ticks Calculate the pwm duty cycle based on the sine wave table
timerUserData.dutyCyclePeriod = dutyCyclePeriod; // Pass dutyCyclePeriod to timerUserData
uint32_t reloadCount = pwmStepPeriod - dutyCyclePeriod; //ticks
alarm_config.alarm_count = dutyCyclePeriod; //ticks
alarm_config.reload_count = reloadCount; //ticks
alarm_config.flags.auto_reload_on_alarm = true;
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config)); // Set the alarm action with the updated alarm_config
tableIndex = (tableIndex + 1) % PWM_STEPS; // Increment the tableIndex
}
}
void app_main() {
gpio_config_t io_conf = {}; //zero-initialize the config structure.
io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << PWM_PIN);
io_conf.pull_down_en = 0; //disable pull-down mode
io_conf.pull_up_en = 0; //disable pull-up mode
gpio_config(&io_conf); //configure GPIO with the given settings
gptimer_config_t timer_config = { // Define the desired timer configuration
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 4 * 1000 * 1000, // 4MHz, 1 tick = 0.25us
};
gptimer_new_timer(&timer_config, &gptimer);
gptimer_event_callbacks_t cbs = {.on_alarm = timer_on_alarm_cb,};
gptimer_register_event_callbacks(gptimer, &cbs, &timerUserData);
gptimer_enable(gptimer);
xTaskCreate(pinOffnotificationFunction, "pinOffTask", configMINIMAL_STACK_SIZE, NULL, 1, &pinOffTask);
// Create the generateSineWaveTask as a FreeRTOS task
xTaskCreate(generateSineWaveTask, "SineWaveTask", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
}
It compiles but does not give a sine wave output on pin 25.
Any help debugging it would be greatly appreciated!
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <esp_timer.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gptimer.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/semphr.h"
#include "rom/ets_sys.h"
#define PWM_PIN GPIO_NUM_25
#define SINE_WAVE_FREQUENCY_MIN 39.5 //automatically double as decimal
#define SINE_WAVE_FREQUENCY_MAX 40.5 //ditto
#define SINE_WAVE_FREQUENCY_STEP 0.001
#define PWM_FREQ_TARGET ((SINE_WAVE_FREQUENCY_MIN + SINE_WAVE_FREQUENCY_MAX) / 2.0) //auto double
#define PWM_STEPS 32
const uint16_t sineWaveTable[PWM_STEPS] = {
2048, 2447, 2831, 3185, 3495, 3750, 3939, 4056,
4095, 4056, 3939, 3750, 3495, 3185, 2831, 2447,
2048, 1649, 1265, 911, 601, 346, 157, 40,
0, 40, 157, 346, 601, 911, 1265, 1649
};
typedef struct {
uint32_t dutyCyclePeriod;
} TimerUserData;
TimerUserData timerUserData; // Global instance of TimerUserData
gptimer_handle_t gptimer = NULL; // Declare the gptimer variable
TaskHandle_t pinOffTask; // Task handle for pinOffnotificationFunction
static bool IRAM_ATTR timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
(void)timer;
(void)edata;
TimerUserData* userData = (TimerUserData*)user_ctx;
uint32_t dutyCyclePeriod = userData->dutyCyclePeriod;
(void)dutyCyclePeriod; // Suppress the unused variable warning
BaseType_t higherPriorityTaskWoken = pdFALSE;
vTaskGenericNotifyGiveFromISR(pinOffTask, 0, &higherPriorityTaskWoken); // Notify the pinOffnotificationFunction task to turn off the PWM pin
portYIELD_FROM_ISR(higherPriorityTaskWoken); // Perform any required context switch if xHigherPriorityTaskWoken is set to pdTRUE
return false;
}
void pinOffnotificationFunction(void *pvParameters) {
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for the notification from timer_on_alarm_cb
gpio_set_level(PWM_PIN, 0); // Set PWM pin to LOW
//vTaskDelay(pdMS_TO_TICKS(1)); // Delay for a short period to avoid immediate re-notification
// note! 1ms = 4000ticks!
ets_delay_us(100); //Stalls execution for #uS
}
}
void generateSineWaveTask(void *pvParameters) {
uint32_t pwmStepPeriod;
uint32_t refFreq = 4 * 1000 * 1000; //Hz
uint8_t tableIndex = 0; // Declare and initialize the tableIndex variable
gptimer_alarm_config_t alarm_config = {
.alarm_count = 0,
.reload_count = 0,
.flags.auto_reload_on_alarm = false,
};
gptimer_start(gptimer);
while (1) {
gpio_set_level(PWM_PIN, 1); // Set PWM pin to HIGH
pwmStepPeriod = refFreq / (PWM_FREQ_TARGET * 32); //ticks
uint16_t tableValue = sineWaveTable[tableIndex];
uint32_t dutyCyclePeriod = (tableValue * pwmStepPeriod) / 65535; // ticks Calculate the pwm duty cycle based on the sine wave table
timerUserData.dutyCyclePeriod = dutyCyclePeriod; // Pass dutyCyclePeriod to timerUserData
uint32_t reloadCount = pwmStepPeriod - dutyCyclePeriod; //ticks
alarm_config.alarm_count = dutyCyclePeriod; //ticks
alarm_config.reload_count = reloadCount; //ticks
alarm_config.flags.auto_reload_on_alarm = true;
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config)); // Set the alarm action with the updated alarm_config
tableIndex = (tableIndex + 1) % PWM_STEPS; // Increment the tableIndex
}
}
void app_main() {
gpio_config_t io_conf = {}; //zero-initialize the config structure.
io_conf.intr_type = GPIO_INTR_DISABLE; //disable interrupt
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << PWM_PIN);
io_conf.pull_down_en = 0; //disable pull-down mode
io_conf.pull_up_en = 0; //disable pull-up mode
gpio_config(&io_conf); //configure GPIO with the given settings
gptimer_config_t timer_config = { // Define the desired timer configuration
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 4 * 1000 * 1000, // 4MHz, 1 tick = 0.25us
};
gptimer_new_timer(&timer_config, &gptimer);
gptimer_event_callbacks_t cbs = {.on_alarm = timer_on_alarm_cb,};
gptimer_register_event_callbacks(gptimer, &cbs, &timerUserData);
gptimer_enable(gptimer);
xTaskCreate(pinOffnotificationFunction, "pinOffTask", configMINIMAL_STACK_SIZE, NULL, 1, &pinOffTask);
// Create the generateSineWaveTask as a FreeRTOS task
xTaskCreate(generateSineWaveTask, "SineWaveTask", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
}