671 lines
18 KiB
C
671 lines
18 KiB
C
/*
|
|
* Copyright (c) 2006-2023, RT-Thread Development Team
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2023-02-14 Rbb666 first version
|
|
*/
|
|
#include <rtthread.h>
|
|
#include "hal_data.h"
|
|
#include <rtdevice.h>
|
|
#include <rthw.h>
|
|
|
|
#include "pwm_audio.h"
|
|
|
|
#define BUFFER_MIN_SIZE (256UL)
|
|
#define SAMPLE_RATE_MAX (48000)
|
|
#define SAMPLE_RATE_MIN (8000)
|
|
#define CHANNEL_LEFT_INDEX (0)
|
|
#define CHANNEL_RIGHT_INDEX (1)
|
|
#define CHANNEL_LEFT_MASK (0x01)
|
|
#define CHANNEL_RIGHT_MASK (0x02)
|
|
#define VOLUME_0DB (16)
|
|
|
|
typedef struct
|
|
{
|
|
char *buf; /**< Original pointer */
|
|
uint32_t volatile head; /**< ending pointer */
|
|
uint32_t volatile tail; /**< Read pointer */
|
|
uint32_t size; /**< Buffer size */
|
|
uint32_t is_give; /**< semaphore give flag */
|
|
rt_sem_t semaphore_rb; /**< Semaphore for ringbuffer */
|
|
|
|
} ringBuf;
|
|
typedef ringBuf *ringbuf_handle_t;
|
|
|
|
typedef struct
|
|
{
|
|
pwm_audio_config_t config; /**< pwm audio config struct */
|
|
timer_cfg_t pwm_timer_cfg; /**< ledc timer config */
|
|
timer_cfg_t gen_timer_cfg; /**< general timer config */
|
|
gpt_instance_ctrl_t *pwm_timer_ctrl; /**< timer group register pointer */
|
|
gpt_instance_ctrl_t *gen_timer_ctrl; /**< timer group register pointer */
|
|
ringbuf_handle_t ringbuf; /**< audio ringbuffer pointer */
|
|
uint32_t channel_mask; /**< channel gpio mask */
|
|
uint32_t channel_set_num; /**< channel audio set number */
|
|
int32_t framerate; /*!< frame rates in Hz */
|
|
int32_t bits_per_sample; /*!< bits per sample (8, 16, 32) */
|
|
int32_t volume; /*!< the volume(-VOLUME_0DB ~ VOLUME_0DB) */
|
|
rt_sem_t sem_complete; /**< Semaphore for play complete */
|
|
|
|
pwm_audio_status_t status;
|
|
} pwm_audio_handle;
|
|
typedef pwm_audio_handle *pwm_audio_handle_t;
|
|
|
|
/**< pwm audio handle pointer */
|
|
static pwm_audio_handle_t g_pwm_audio_handle = NULL;
|
|
|
|
/**
|
|
* Ringbuffer for pwm audio
|
|
*/
|
|
static rt_err_t rb_destroy(ringbuf_handle_t rb)
|
|
{
|
|
if (rb == NULL)
|
|
{
|
|
return RT_ERROR;
|
|
}
|
|
|
|
if (rb->buf)
|
|
{
|
|
rt_free(rb->buf);
|
|
rb->buf = NULL;
|
|
}
|
|
|
|
if (rb->semaphore_rb)
|
|
{
|
|
rt_sem_delete(rb->semaphore_rb);
|
|
}
|
|
|
|
rt_free(rb);
|
|
return RT_EOK;
|
|
}
|
|
|
|
static ringbuf_handle_t rb_create(uint32_t size)
|
|
{
|
|
if (size < (BUFFER_MIN_SIZE << 2))
|
|
{
|
|
rt_kprintf("Invalid buffer size, Minimum = %d", (int32_t)(BUFFER_MIN_SIZE << 2));
|
|
return NULL;
|
|
}
|
|
|
|
ringbuf_handle_t rb = NULL;
|
|
char *buf = NULL;
|
|
|
|
do
|
|
{
|
|
bool _success =
|
|
(
|
|
(rb = rt_malloc(sizeof(ringBuf))) &&
|
|
(buf = rt_malloc(size)) &&
|
|
(rb->semaphore_rb = rt_sem_create("rb_sem", 0, RT_IPC_FLAG_PRIO))
|
|
|
|
);
|
|
|
|
if (!_success)
|
|
{
|
|
break;
|
|
}
|
|
|
|
rb->is_give = 0;
|
|
rb->buf = buf;
|
|
rb->head = rb->tail = 0;
|
|
rb->size = size;
|
|
return rb;
|
|
|
|
}
|
|
while (0);
|
|
|
|
rb_destroy(rb);
|
|
return NULL;
|
|
}
|
|
|
|
static uint32_t rb_get_count(ringbuf_handle_t rb)
|
|
{
|
|
uint32_t tail = rb->tail;
|
|
|
|
if (rb->head >= tail)
|
|
{
|
|
return (rb->head - tail);
|
|
}
|
|
|
|
return (rb->size - (tail - rb->head));
|
|
}
|
|
|
|
static uint32_t rb_get_free(ringbuf_handle_t rb)
|
|
{
|
|
/** < Free a byte to judge the ringbuffer direction */
|
|
return (rb->size - rb_get_count(rb) - 1);
|
|
}
|
|
|
|
static rt_err_t rb_flush(ringbuf_handle_t rb)
|
|
{
|
|
rb->tail = rb->head = 0;
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t rb_read_byte(ringbuf_handle_t rb, uint8_t *outdata)
|
|
{
|
|
uint32_t tail = rb->tail;
|
|
|
|
if (tail == rb->head)
|
|
{
|
|
return RT_ERROR;
|
|
}
|
|
|
|
// Send a byte from the buffer
|
|
*outdata = rb->buf[tail];
|
|
|
|
// Update tail position
|
|
tail++;
|
|
|
|
if (tail == rb->size)
|
|
{
|
|
tail = 0;
|
|
}
|
|
|
|
rb->tail = tail;
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t rb_write_byte(ringbuf_handle_t rb, const uint8_t indata)
|
|
{
|
|
// Calculate next head
|
|
uint32_t next_head = rb->head + 1;
|
|
|
|
if (next_head == rb->size)
|
|
{
|
|
next_head = 0;
|
|
}
|
|
|
|
if (next_head == rb->tail)
|
|
{
|
|
return RT_ERROR;
|
|
}
|
|
|
|
// Store data and advance head
|
|
rb->buf[rb->head] = indata;
|
|
rb->head = next_head;
|
|
return RT_EOK;
|
|
}
|
|
|
|
static rt_err_t rb_wait_semaphore(ringbuf_handle_t rb, rt_tick_t ticks_to_wait)
|
|
{
|
|
rb->is_give = 0; /**< As long as it's written, it's allowed to give semaphore again */
|
|
|
|
if (rt_sem_take(rb->semaphore_rb, ticks_to_wait) == RT_EOK)
|
|
{
|
|
return RT_EOK;
|
|
}
|
|
|
|
return RT_ERROR;
|
|
}
|
|
|
|
rt_err_t pwm_audio_wait_complete(rt_tick_t ticks_to_wait)
|
|
{
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
|
|
if (rt_sem_take(handle->sem_complete, ticks_to_wait) == RT_EOK)
|
|
{
|
|
return RT_EOK;
|
|
}
|
|
|
|
return RT_ERROR;
|
|
}
|
|
|
|
rt_err_t pwm_audio_init(const pwm_audio_config_t *cfg)
|
|
{
|
|
rt_err_t res = RT_EOK;
|
|
|
|
pwm_audio_handle_t handle = NULL;
|
|
|
|
int level = rt_hw_interrupt_disable();
|
|
|
|
handle = rt_malloc(sizeof(pwm_audio_handle));
|
|
RT_ASSERT(handle != NULL);
|
|
memset(handle, 0, sizeof(pwm_audio_handle));
|
|
|
|
handle->ringbuf = rb_create(cfg->ringbuf_len);
|
|
RT_ASSERT(handle->ringbuf != NULL);
|
|
|
|
handle->sem_complete = rt_sem_create("pwm_cpl_sem", 0, RT_IPC_FLAG_PRIO);
|
|
RT_ASSERT(handle->sem_complete != NULL);
|
|
|
|
handle->config = *cfg;
|
|
g_pwm_audio_handle = handle;
|
|
|
|
handle->channel_mask = 0;
|
|
|
|
if (handle->config.gpio_num_left >= 0)
|
|
{
|
|
handle->channel_mask |= CHANNEL_LEFT_MASK;
|
|
}
|
|
|
|
if (handle->config.gpio_num_right >= 0)
|
|
{
|
|
handle->channel_mask |= CHANNEL_RIGHT_MASK;
|
|
}
|
|
|
|
RT_ASSERT(0 != handle->channel_mask);
|
|
|
|
//
|
|
handle->pwm_timer_cfg = g_timer6_cfg;
|
|
handle->pwm_timer_ctrl = &g_timer6_ctrl;
|
|
|
|
R_GPT_Open(handle->pwm_timer_ctrl, &handle->pwm_timer_cfg);
|
|
R_GPT_Start(handle->pwm_timer_ctrl);
|
|
//
|
|
|
|
/**< set a initial parameter */
|
|
// res = pwm_audio_set_param(16000, 8, 2);
|
|
|
|
handle->status = PWM_AUDIO_STATUS_IDLE;
|
|
|
|
rt_hw_interrupt_enable(level);
|
|
|
|
return res;
|
|
}
|
|
|
|
rt_err_t pwm_audio_set_param(int rate, uint8_t bits, int ch)
|
|
{
|
|
rt_err_t res = RT_EOK;
|
|
|
|
RT_ASSERT(g_pwm_audio_handle->status != PWM_AUDIO_STATUS_BUSY);
|
|
RT_ASSERT(rate <= SAMPLE_RATE_MAX && rate >= SAMPLE_RATE_MIN);
|
|
RT_ASSERT(bits == 32 || bits == 16 || bits == 8);
|
|
RT_ASSERT(ch <= 2 && ch >= 1);
|
|
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
|
|
handle->framerate = rate;
|
|
handle->bits_per_sample = bits;
|
|
handle->channel_set_num = ch;
|
|
|
|
handle->gen_timer_cfg = g_timer2_cfg;
|
|
handle->gen_timer_ctrl = &g_timer2_ctrl;
|
|
|
|
timer_cfg_t *config = NULL;
|
|
config = (struct st_timer_cfg *)&g_timer2_cfg;
|
|
|
|
R_GPT_Open(handle->gen_timer_ctrl, &handle->gen_timer_cfg);
|
|
R_GPT_Start(handle->gen_timer_ctrl);
|
|
|
|
return res;
|
|
}
|
|
|
|
rt_err_t pwm_audio_set_volume(int8_t volume)
|
|
{
|
|
if (volume < 0)
|
|
{
|
|
RT_ASSERT(-volume <= VOLUME_0DB);
|
|
}
|
|
else
|
|
{
|
|
RT_ASSERT(volume <= VOLUME_0DB);
|
|
}
|
|
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
handle->volume = volume + VOLUME_0DB;
|
|
|
|
rt_kprintf("set volume to:%d\n", handle->volume);
|
|
return RT_EOK;
|
|
}
|
|
|
|
rt_err_t pwm_audio_write(uint8_t *inbuf, size_t inbuf_len, size_t *bytes_written, rt_tick_t ticks_to_wait)
|
|
{
|
|
rt_err_t res = RT_EOK;
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
|
|
RT_ASSERT(inbuf != NULL && bytes_written != NULL && inbuf_len != 0);
|
|
|
|
*bytes_written = 0;
|
|
ringbuf_handle_t rb = handle->ringbuf;
|
|
rt_tick_t start_ticks = rt_tick_get();
|
|
|
|
while (inbuf_len)
|
|
{
|
|
if (RT_EOK == rb_wait_semaphore(rb, ticks_to_wait))
|
|
{
|
|
uint32_t free = rb_get_free(rb);
|
|
uint32_t bytes_can_write = inbuf_len;
|
|
|
|
if (inbuf_len > free)
|
|
{
|
|
bytes_can_write = free;
|
|
}
|
|
|
|
bytes_can_write &= 0xfffffffc; /**< Aligned data, bytes_can_write should be an integral multiple of 4 */
|
|
|
|
if (0 == bytes_can_write)
|
|
{
|
|
*bytes_written += inbuf_len; /**< Discard the last misaligned bytes of data directly */
|
|
return RT_EOK;
|
|
}
|
|
|
|
/**< Get the difference between PWM resolution and audio samplewidth */
|
|
int8_t shift = handle->bits_per_sample - handle->config.duty_resolution;
|
|
uint32_t len = bytes_can_write;
|
|
|
|
switch (handle->bits_per_sample)
|
|
{
|
|
case 8:
|
|
{
|
|
if (shift < 0)
|
|
{
|
|
/**< When the PWM resolution is greater than 8 bits, the value needs to be expanded */
|
|
uint16_t value;
|
|
uint8_t temp;
|
|
shift = -shift;
|
|
len >>= 1;
|
|
bytes_can_write >>= 1;
|
|
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
temp = (inbuf[i] * handle->volume / VOLUME_0DB) + 0x7f; /**< offset */
|
|
value = temp << shift;
|
|
rb_write_byte(rb, value);
|
|
rb_write_byte(rb, value >> 8);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint8_t value;
|
|
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
value = (inbuf[i] * handle->volume / VOLUME_0DB) + 0x7f; /**< offset */
|
|
rb_write_byte(rb, value);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 16:
|
|
{
|
|
len >>= 1;
|
|
uint16_t *buf_16b = (uint16_t *)inbuf;
|
|
static uint16_t value_16b;
|
|
int16_t temp;
|
|
|
|
if (handle->config.duty_resolution > 8)
|
|
{
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
temp = buf_16b[i];
|
|
temp = temp * handle->volume / VOLUME_0DB;
|
|
value_16b = temp + 0x7fff; /**< offset */
|
|
value_16b >>= shift;
|
|
rb_write_byte(rb, value_16b);
|
|
rb_write_byte(rb, value_16b >> 8);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* When the PWM resolution is 8 bit, only one byte is transmitted
|
|
*/
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
temp = buf_16b[i];
|
|
temp = temp * handle->volume / VOLUME_0DB;
|
|
value_16b = temp + 0x7fff; /**< offset */
|
|
value_16b >>= shift;
|
|
rb_write_byte(rb, value_16b);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 32:
|
|
{
|
|
len >>= 2;
|
|
uint32_t *buf_32b = (uint32_t *)inbuf;
|
|
uint32_t value;
|
|
int32_t temp;
|
|
|
|
if (handle->config.duty_resolution > 8)
|
|
{
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
temp = buf_32b[i];
|
|
temp = temp * handle->volume / VOLUME_0DB;
|
|
value = temp + 0x7fffffff; /**< offset */
|
|
value >>= shift;
|
|
rb_write_byte(rb, value);
|
|
rb_write_byte(rb, value >> 8);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* When the PWM resolution is 8 bit, only one byte is transmitted
|
|
*/
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
temp = buf_32b[i];
|
|
temp = temp * handle->volume / VOLUME_0DB;
|
|
value = temp + 0x7fffffff; /**< offset */
|
|
value >>= shift;
|
|
rb_write_byte(rb, value);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
inbuf += bytes_can_write;
|
|
inbuf_len -= bytes_can_write;
|
|
*bytes_written += bytes_can_write;
|
|
}
|
|
else
|
|
{
|
|
res = RT_ERROR;
|
|
}
|
|
|
|
if ((rt_tick_get() - start_ticks) >= ticks_to_wait)
|
|
{
|
|
return res;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Note:
|
|
* In order to improve efficiency, register is operated directly
|
|
*/
|
|
static inline void ledc_set_left_duty_fast(uint32_t duty_val)
|
|
{
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
// *g_ledc_left_duty_val = (duty_val) << 4; /* Discard decimal part */
|
|
R_GPT_DutyCycleSet(handle->pwm_timer_ctrl, duty_val, GPT_IO_PIN_GTIOCA);
|
|
}
|
|
|
|
static inline void ledc_set_right_duty_fast(uint32_t duty_val)
|
|
{
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
// *g_ledc_right_duty_val = (duty_val) << 4; /* Discard decimal part */
|
|
R_GPT_DutyCycleSet(handle->pwm_timer_ctrl, duty_val, GPT_IO_PIN_GTIOCB);
|
|
}
|
|
|
|
void cb_timer2(timer_callback_args_t *p_args)
|
|
{
|
|
rt_interrupt_enter();
|
|
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
|
|
if (handle == NULL)
|
|
{
|
|
rt_kprintf("pwm audio not initialized\n");
|
|
return;
|
|
}
|
|
|
|
static uint8_t wave_h, wave_l;
|
|
static uint16_t value;
|
|
ringbuf_handle_t rb = handle->ringbuf;
|
|
|
|
/**
|
|
* It is believed that the channel configured with GPIO needs to output sound
|
|
*/
|
|
if (handle->channel_mask & CHANNEL_LEFT_MASK)
|
|
{
|
|
if (handle->config.duty_resolution > 8)
|
|
{
|
|
if (rb_get_count(rb) > 1)
|
|
{
|
|
rb_read_byte(rb, &wave_l);
|
|
rb_read_byte(rb, &wave_h);
|
|
value = ((wave_h << 8) | wave_l);
|
|
ledc_set_left_duty_fast(value);/**< set the PWM duty */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RT_EOK == rb_read_byte(rb, &wave_h))
|
|
{
|
|
ledc_set_left_duty_fast(wave_h);/**< set the PWM duty */
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If two gpios are configured, but the audio data has only one channel, copy the data to the right channel
|
|
* Instead, the right channel data is discarded
|
|
*/
|
|
if (handle->channel_mask & CHANNEL_RIGHT_MASK)
|
|
{
|
|
if (handle->channel_set_num == 1)
|
|
{
|
|
if (handle->config.duty_resolution > 8)
|
|
{
|
|
ledc_set_right_duty_fast(value);/**< set the PWM duty */
|
|
}
|
|
else
|
|
{
|
|
ledc_set_right_duty_fast(wave_h);/**< set the PWM duty */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (handle->config.duty_resolution > 8)
|
|
{
|
|
if (rb_get_count(rb) > 1)
|
|
{
|
|
rb_read_byte(rb, &wave_l);
|
|
rb_read_byte(rb, &wave_h);
|
|
value = ((wave_h << 8) | wave_l);
|
|
ledc_set_right_duty_fast(value);/**< set the PWM duty */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (RT_EOK == rb_read_byte(rb, &wave_h))
|
|
{
|
|
ledc_set_right_duty_fast(wave_h);/**< set the PWM duty */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (handle->channel_set_num == 2)
|
|
{
|
|
/**
|
|
* Discard the right channel data only if the right channel is configured but the audio data is stereo
|
|
* Read buffer but do nothing
|
|
*/
|
|
if (handle->config.duty_resolution > 8)
|
|
{
|
|
if (rb_get_count(rb) > 1)
|
|
{
|
|
rb_read_byte(rb, &wave_h);
|
|
rb_read_byte(rb, &wave_h);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rb_read_byte(rb, &wave_h);
|
|
}
|
|
|
|
rb_read_byte(rb, &wave_l);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send semaphore when buffer free is more than BUFFER_MIN_SIZE
|
|
*/
|
|
if (0 == rb->is_give && rb_get_free(rb) > BUFFER_MIN_SIZE)
|
|
{
|
|
/**< The execution time of the following code is 2.71 microsecond */
|
|
rb->is_give = 1; /**< To prevent multiple give semaphores */
|
|
|
|
int err = -RT_ERROR;
|
|
|
|
rt_interrupt_enter();
|
|
err = rt_sem_release(rb->semaphore_rb);
|
|
rt_interrupt_leave();
|
|
|
|
if (rb_get_count(rb) <= 1)
|
|
{
|
|
rt_interrupt_enter();
|
|
err = rt_sem_release(handle->sem_complete);
|
|
rt_interrupt_leave();
|
|
}
|
|
if (err != RT_ERROR)
|
|
{
|
|
rt_thread_yield();
|
|
}
|
|
}
|
|
|
|
rt_interrupt_leave();
|
|
}
|
|
|
|
rt_err_t pwm_audio_start(void)
|
|
{
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
RT_ASSERT(NULL != handle);
|
|
RT_ASSERT(handle->status == PWM_AUDIO_STATUS_IDLE);
|
|
|
|
handle->status = PWM_AUDIO_STATUS_BUSY;
|
|
|
|
/**< timer enable interrupt */
|
|
int level = rt_hw_interrupt_disable();
|
|
R_GPT_Start(handle->gen_timer_ctrl);
|
|
rt_hw_interrupt_enable(level);
|
|
|
|
return RT_EOK;
|
|
}
|
|
|
|
rt_err_t pwm_audio_stop(void)
|
|
{
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
|
|
/**< just disable timer ,keep pwm output to reduce switching nosie */
|
|
/**< timer disable interrupt */
|
|
int level = rt_hw_interrupt_disable();
|
|
R_GPT_Stop(handle->gen_timer_ctrl);
|
|
rt_hw_interrupt_enable(level);
|
|
|
|
rb_flush(handle->ringbuf); /**< flush ringbuf, avoid play noise */
|
|
handle->status = PWM_AUDIO_STATUS_IDLE;
|
|
return RT_EOK;
|
|
}
|
|
|
|
rt_err_t pwm_audio_deinit(void)
|
|
{
|
|
pwm_audio_handle_t handle = g_pwm_audio_handle;
|
|
RT_ASSERT(handle != NULL);
|
|
|
|
handle->status = PWM_AUDIO_STATUS_UN_INIT;
|
|
R_GPT_Close(handle->pwm_timer_ctrl);
|
|
R_GPT_Close(handle->gen_timer_ctrl);
|
|
rt_sem_delete(handle->sem_complete);
|
|
rb_destroy(handle->ringbuf);
|
|
rt_free(handle);
|
|
return RT_EOK;
|
|
}
|