summaryrefslogtreecommitdiff
path: root/boards/dk-tm4c129x/drivers/sound.c
diff options
context:
space:
mode:
authorYuval Adam <yuv.adm@gmail.com>2014-06-29 12:34:32 +0300
committerYuval Adam <yuv.adm@gmail.com>2014-06-29 12:34:32 +0300
commitc3e4c9a25c2910d2d66d52215b3406b13d5b23d5 (patch)
treeadded370d1e356901f8579f076e3263fb0464db7 /boards/dk-tm4c129x/drivers/sound.c
parent990090a4cc9070837d31e66b58d40f0c3d038741 (diff)
Add more board models
Diffstat (limited to 'boards/dk-tm4c129x/drivers/sound.c')
-rw-r--r--boards/dk-tm4c129x/drivers/sound.c688
1 files changed, 688 insertions, 0 deletions
diff --git a/boards/dk-tm4c129x/drivers/sound.c b/boards/dk-tm4c129x/drivers/sound.c
new file mode 100644
index 0000000..56f84e0
--- /dev/null
+++ b/boards/dk-tm4c129x/drivers/sound.c
@@ -0,0 +1,688 @@
+//*****************************************************************************
+//
+// sound.c - Sound driver for the speaker on the DK-TM4C129X.
+//
+// Copyright (c) 2013-2014 Texas Instruments Incorporated. All rights reserved.
+// Software License Agreement
+//
+// Texas Instruments (TI) is supplying this software for use solely and
+// exclusively on TI's microcontroller products. The software is owned by
+// TI and/or its suppliers, and is protected under applicable copyright
+// laws. You may not combine this software with "viral" open-source
+// software in order to form a larger program.
+//
+// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
+// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
+// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
+// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
+// DAMAGES, FOR ANY REASON WHATSOEVER.
+//
+// This is part of revision 2.1.0.12573 of the DK-TM4C129X Firmware Package.
+//
+//*****************************************************************************
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "inc/hw_ints.h"
+#include "inc/hw_memmap.h"
+#include "inc/hw_timer.h"
+#include "inc/hw_types.h"
+#include "driverlib/gpio.h"
+#include "driverlib/interrupt.h"
+#include "driverlib/rom.h"
+#include "driverlib/sysctl.h"
+#include "driverlib/timer.h"
+#include "drivers/sound.h"
+
+//*****************************************************************************
+//
+//! \addtogroup sound_api
+//! @{
+//
+//*****************************************************************************
+
+//*****************************************************************************
+//
+// This structure defines the internal state of the sound driver.
+//
+//*****************************************************************************
+typedef struct
+{
+ //
+ // The number of clocks per PWM period.
+ //
+ uint32_t ui32Period;
+
+ //
+ // A set of flags indicating the mode of the sound driver.
+ //
+ volatile uint32_t ui32Flags;
+
+ //
+ // A pointer to the sound buffer being played.
+ //
+ const int16_t *pi16Buffer;
+
+ //
+ // The length of the sound buffer, in bytes.
+ //
+ uint32_t ui32Length;
+
+ //
+ // The current playback offset into the sound buffer.
+ //
+ uint32_t ui32Offset;
+
+ //
+ // The volume to playback the sound stream. This is a value between 0
+ // (for silence) and 256 (for full volume).
+ //
+ int32_t i32Volume;
+
+ //
+ // The previous and current sound samples, used for interpolating from
+ // 8 kHz to 64 kHz sound.
+ //
+ int16_t pi16Samples[2];
+
+ //
+ // The sound step, which corresponds to the current interpolation point
+ // between the previous and current sound samples.
+ //
+ int32_t i32Step;
+
+ //
+ // The current requested rate adjustment. This is cleared when the
+ // the adjustment is made.
+ //
+ int32_t i32RateAdjust;
+
+ //
+ // The callback function that indicates when half of the sound buffer has
+ // bene played and is therefore ready to be refilled.
+ //
+ void (*pfnCallback)(uint32_t ui32Half);
+}
+tSoundState;
+
+//*****************************************************************************
+//
+// The flags that are in tSoundState.ui32Flags.
+//
+//*****************************************************************************
+#define SOUND_FLAG_STARTUP 0
+#define SOUND_FLAG_SHUTDOWN 1
+#define SOUND_FLAG_PLAY 2
+#define SOUND_FLAG_8KHZ 3
+#define SOUND_FLAG_16KHZ 4
+#define SOUND_FLAG_32KHZ 5
+#define SOUND_FLAG_64KHZ 6
+
+//*****************************************************************************
+//
+// The current state of the sound driver.
+//
+//*****************************************************************************
+static tSoundState g_sSoundState;
+
+//*****************************************************************************
+//
+//! Handles the TIMER5A interrupt.
+//!
+//! This function responds to the TIMER5A interrupt, updating the duty cycle of
+//! the output waveform in order to produce sound. It is the application's
+//! responsibility to ensure that this function is called in response to the
+//! TIMER5A interrupt, typically by installing it in the vector table as the
+//! handler for the TIMER5A interrupt.
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+SoundIntHandler(void)
+{
+ int32_t i32DutyCycle;
+
+ //
+ // If there is an adjustment to be made, the apply it and set allow the
+ // update to be done on the next load.
+ //
+ if(g_sSoundState.i32RateAdjust)
+ {
+ g_sSoundState.ui32Period += g_sSoundState.i32RateAdjust;
+ g_sSoundState.i32RateAdjust = 0;
+ TimerLoadSet(TIMER5_BASE, TIMER_A, g_sSoundState.ui32Period);
+ }
+
+ //
+ // Clear the timer interrupt.
+ //
+ ROM_TimerIntClear(TIMER5_BASE, TIMER_CAPA_EVENT);
+
+ //
+ // See if the startup ramp is in progress.
+ //
+ if(HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_STARTUP))
+ {
+ //
+ // Increment the ramp count.
+ //
+ g_sSoundState.i32Step++;
+
+ //
+ // Increase the pulse width of the output by one clock.
+ //
+ ROM_TimerMatchSet(TIMER5_BASE, TIMER_A, g_sSoundState.i32Step);
+
+ //
+ // See if this was the last step of the ramp.
+ //
+ if(g_sSoundState.i32Step >= (g_sSoundState.ui32Period / 2))
+ {
+ //
+ // Indicate that the startup ramp has completed.
+ //
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_STARTUP) = 0;
+
+ //
+ // Set the step back to zero for the start of audio playback.
+ //
+ g_sSoundState.i32Step = 0;
+ }
+
+ //
+ // There is nothing further to be done.
+ //
+ return;
+ }
+
+ //
+ // See if the shutdown ramp is in progress.
+ //
+ if(HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_SHUTDOWN))
+ {
+ //
+ // See if this was the last step of the ramp.
+ //
+ if(g_sSoundState.i32Step == 1)
+ {
+ //
+ // Disable the output signals.
+ //
+ ROM_TimerMatchSet(TIMER5_BASE, TIMER_A, g_sSoundState.ui32Period);
+
+ //
+ // Clear the sound flags.
+ //
+ g_sSoundState.ui32Flags = 0;
+
+ //
+ // Disable the speaker amp.
+ //
+ ROM_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_4, 0);
+ }
+ else
+ {
+ //
+ // Decrement the ramp count.
+ //
+ g_sSoundState.i32Step--;
+
+ //
+ // Decrease the pulse width of the output by one clock.
+ //
+ ROM_TimerMatchSet(TIMER5_BASE, TIMER_A, g_sSoundState.i32Step);
+ }
+
+ //
+ // There is nothing further to be done.
+ //
+ return;
+ }
+
+ //
+ // Compute the value of the PCM sample based on the blended average of the
+ // previous and current samples. It should be noted that linear
+ // interpolation does not produce the best results with sound (it produces
+ // a significant amount of harmonic aliasing) but it is fast.
+ //
+ i32DutyCycle =
+ (((g_sSoundState.pi16Samples[0] * (8 - g_sSoundState.i32Step)) +
+ (g_sSoundState.pi16Samples[1] * g_sSoundState.i32Step)) / 8);
+
+ //
+ // Adjust the magnitude of the sample based on the current volume. Since a
+ // multiplicative volume control is implemented, the volume value
+ // results in nearly linear volume adjustment if it is squared.
+ //
+ i32DutyCycle = (((i32DutyCycle * g_sSoundState.i32Volume *
+ g_sSoundState.i32Volume) / 65536) + 32768);
+
+ //
+ // Set the PWM duty cycle based on this PCM sample.
+ //
+ i32DutyCycle = (g_sSoundState.ui32Period * i32DutyCycle) / 65536;
+ ROM_TimerMatchSet(TIMER5_BASE, TIMER_A, i32DutyCycle);
+
+ //
+ // Increment the sound step based on the sample rate.
+ //
+ if(HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_8KHZ))
+ {
+ g_sSoundState.i32Step = (g_sSoundState.i32Step + 1) & 7;
+ }
+ else if(HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_16KHZ))
+ {
+ g_sSoundState.i32Step = (g_sSoundState.i32Step + 2) & 7;
+ }
+ else if(HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_32KHZ))
+ {
+ g_sSoundState.i32Step = (g_sSoundState.i32Step + 4) & 7;
+ }
+
+ //
+ // See if the next sample has been reached.
+ //
+ if(g_sSoundState.i32Step == 0)
+ {
+ //
+ // Copy the current sample to the previous sample.
+ //
+ g_sSoundState.pi16Samples[0] = g_sSoundState.pi16Samples[1];
+
+ //
+ // Get the next sample from the buffer.
+ //
+ g_sSoundState.pi16Samples[1] =
+ g_sSoundState.pi16Buffer[g_sSoundState.ui32Offset];
+
+ //
+ // Increment the buffer pointer.
+ //
+ g_sSoundState.ui32Offset++;
+ if(g_sSoundState.ui32Offset == g_sSoundState.ui32Length)
+ {
+ g_sSoundState.ui32Offset = 0;
+ }
+
+ //
+ // Call the callback function if one of the half-buffers has been
+ // consumed.
+ //
+ if(g_sSoundState.pfnCallback)
+ {
+ if(g_sSoundState.ui32Offset == 0)
+ {
+ g_sSoundState.pfnCallback(1);
+ }
+ else if(g_sSoundState.ui32Offset == (g_sSoundState.ui32Length / 2))
+ {
+ g_sSoundState.pfnCallback(0);
+ }
+ }
+ }
+}
+
+//*****************************************************************************
+//
+//! Initializes the sound driver.
+//!
+//! \param ui32SysClock is the frequency of the system clock.
+//!
+//! This function initializes the sound driver, preparing it to output sound
+//! data to the speaker.
+//!
+//! The system clock should be as high as possible; lower clock rates reduces
+//! the quality of the produced sound. For the best quality sound, the system
+//! should be clocked at 120 MHz.
+//!
+//! \note In order for the sound driver to function properly, the sound driver
+//! interrupt handler (SoundIntHandler()) must be installed into the vector
+//! table for the timer 5 subtimer A interrupt.
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+SoundInit(uint32_t ui32SysClock)
+{
+ //
+ // Enable the peripherals used by the sound driver.
+ //
+ ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER5);
+
+ //
+ // Compute the PWM period based on the system clock.
+ //
+ g_sSoundState.ui32Period = ui32SysClock / 64000;
+
+ //
+ // Set the default volume.
+ //
+ g_sSoundState.i32Volume = 255;
+
+ //
+ // Configure the timer to run in PWM mode.
+ //
+ if((HWREG(TIMER5_BASE + TIMER_O_CTL) & TIMER_CTL_TBEN) == 0)
+ {
+ ROM_TimerConfigure(TIMER5_BASE, (TIMER_CFG_SPLIT_PAIR |
+ TIMER_CFG_A_PWM |
+ TIMER_CFG_B_PERIODIC));
+ }
+ ROM_TimerLoadSet(TIMER5_BASE, TIMER_A, g_sSoundState.ui32Period - 1);
+ ROM_TimerMatchSet(TIMER5_BASE, TIMER_A, g_sSoundState.ui32Period);
+ ROM_TimerControlLevel(TIMER5_BASE, TIMER_A, true);
+
+ //
+ // Update the timer values on timeouts and not immediately.
+ //
+ TimerUpdateMode(TIMER5_BASE, TIMER_A, TIMER_UP_LOAD_TIMEOUT |
+ TIMER_UP_MATCH_TIMEOUT);
+
+ //
+ // Configure the timer to generate an interrupt at every time-out event.
+ //
+ ROM_TimerIntEnable(TIMER5_BASE, TIMER_CAPA_EVENT);
+
+ //
+ // Enable the timer. At this point, the timer generates an interrupt
+ // every 15.625 us.
+ //
+ ROM_TimerEnable(TIMER5_BASE, TIMER_A);
+ ROM_IntEnable(INT_TIMER5A);
+
+ //
+ // Clear the sound flags.
+ //
+ g_sSoundState.ui32Flags = 0;
+}
+
+//*****************************************************************************
+//
+//! Make adjustments to the sample period of the PWM audio.
+//!
+//! \param i32RateAdjust is a signed value of the adjustment to make to the
+//! current sample period.
+//!
+//! This function allows the sample period to be adjusted if the application
+//! needs to make small adjustments to the playback rate of the audio. This
+//! should only be used to makke smaller adjustments to the sample rate since
+//! large changes cause distortion in the output.
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+SoundPeriodAdjust(int32_t i32RateAdjust)
+{
+ g_sSoundState.i32RateAdjust += i32RateAdjust;
+}
+
+//*****************************************************************************
+//
+//! Starts playback of a sound stream.
+//!
+//! \param pi16Buffer is a pointer to the buffer that contains the sound to
+//! play.
+//! \param ui32Length is the length of the buffer in samples. This should be
+//! a multiple of two.
+//! \param ui32Rate is the sound playback rate; valid values are 8000, 16000,
+//! 32000, and 64000.
+//! \param pfnCallback is the callback function that is called when either half
+//! of the sound buffer has been played.
+//!
+//! This function starts the playback of a sound stream contained in an
+//! audio ping-pong buffer. The buffer is played repeatedly until
+//! SoundStop() is called. Playback of the sound stream begins
+//! immediately, so the buffer should be pre-filled with the initial sound
+//! data prior to calling this function.
+//!
+//! \return Returns \b true if playback was started and \b false if it could
+//! not be started (because something is already playing).
+//
+//*****************************************************************************
+bool
+SoundStart(int16_t *pi16Buffer, uint32_t ui32Length, uint32_t ui32Rate,
+ void (*pfnCallback)(uint32_t ui32Half))
+{
+ //
+ // Return without playing the buffer if something is already playing.
+ //
+ if(g_sSoundState.ui32Flags)
+ {
+ return(false);
+ }
+
+ //
+ // Set the sample rate flag.
+ //
+ if(ui32Rate == 8000)
+ {
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_8KHZ) = 1;
+ }
+ else if(ui32Rate == 16000)
+ {
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_16KHZ) = 1;
+ }
+ else if(ui32Rate == 32000)
+ {
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_32KHZ) = 1;
+ }
+ else if(ui32Rate == 64000)
+ {
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_64KHZ) = 1;
+ }
+ else
+ {
+ return(false);
+ }
+
+ //
+ // Enable the speaker amp.
+ //
+ ROM_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_4, GPIO_PIN_4);
+
+ //
+ // Save the pointer to the buffer.
+ //
+ g_sSoundState.pi16Buffer = pi16Buffer;
+ g_sSoundState.ui32Length = ui32Length;
+
+ //
+ // Save the pointer to the callback function.
+ //
+ g_sSoundState.pfnCallback = pfnCallback;
+
+ //
+ // Start playback from the beginning of the buffer.
+ //
+ g_sSoundState.ui32Offset = 0;
+
+ //
+ // Initialize the sample buffer with silence.
+ //
+ g_sSoundState.pi16Samples[0] = 0;
+ g_sSoundState.pi16Samples[1] = 0;
+
+ //
+ // Start playback of the stream.
+ //
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_STARTUP) = 1;
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_PLAY) = 1;
+
+ //
+ // Set the step for the startup ramp.
+ //
+ g_sSoundState.i32Step = 1;
+
+ //
+ // Enable the timer interrupt.
+ //
+ ROM_TimerMatchSet(TIMER5_BASE, TIMER_A, 1);
+
+ //
+ // Success.
+ //
+ return(true);
+}
+
+//*****************************************************************************
+//
+//! Stops playback of the current sound stream.
+//!
+//! This function immediately stops playback of the current sound stream. As
+//! a result, the output is changed directly to the mid-point, possibly
+//! resulting in a pop or click. It is then ramped down to no output,
+//! eliminating the current draw through the amplifier and speaker.
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+SoundStop(void)
+{
+ //
+ // See if playback is in progress.
+ //
+ if((g_sSoundState.ui32Flags != 0) &&
+ (HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_SHUTDOWN) == 0))
+ {
+ //
+ // Temporarily disable the timer interrupt.
+ //
+ ROM_IntDisable(INT_TIMER5A);
+
+ //
+ // Clear the sound flags and set the shutdown flag (to try to avoid a
+ // pop, though one may still occur based on the current position of the
+ // output waveform).
+ //
+ g_sSoundState.ui32Flags = 0;
+ HWREGBITW(&g_sSoundState.ui32Flags, SOUND_FLAG_SHUTDOWN) = 1;
+
+ //
+ // Set the shutdown step to the first.
+ //
+ g_sSoundState.i32Step = g_sSoundState.ui32Period / 2;
+
+ //
+ // Reenable the timer interrupt.
+ //
+ ROM_IntEnable(INT_TIMER5A);
+ }
+}
+
+//*****************************************************************************
+//
+//! Determines if the sound driver is busy.
+//!
+//! This function determines if the sound driver is busy, either performing the
+//! startup or shutdown ramp for the speaker or playing a sound stream.
+//!
+//! \return Returns \b true if the sound driver is busy and \b false otherwise.
+//
+//*****************************************************************************
+bool
+SoundBusy(void)
+{
+ //
+ // The sound driver is busy if the sound flags are not zero.
+ //
+ return(g_sSoundState.ui32Flags != 0);
+}
+
+//*****************************************************************************
+//
+//! Sets the volume of the sound playback.
+//!
+//! \param i32Volume is the volume of the sound playback, specified as a value
+//! between 0 (for silence) and 255 (for full volume).
+//!
+//! This function sets the volume of the sound playback. Setting the volume to
+//! 0 mutes the output, while setting the volume to 256 plays the sound
+//! stream without any volume adjustment (that is, full volume).
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+SoundVolumeSet(int32_t i32Volume)
+{
+ //
+ // Set the volume mulitplier to be used.
+ //
+ g_sSoundState.i32Volume = i32Volume;
+}
+
+//*****************************************************************************
+//
+//! Increases the volume of the sound playback.
+//!
+//! \param i32Volume is the amount by which to increase the volume of the
+//! sound playback, specified as a value between 0 (for no adjustment) and 255
+//! maximum adjustment).
+//!
+//! This function increases the volume of the sound playback relative to the
+//! current volume.
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+SoundVolumeUp(int32_t i32Volume)
+{
+ //
+ // Compute the new volume, limiting to the maximum if required.
+ //
+ i32Volume = g_sSoundState.i32Volume + i32Volume;
+ if(i32Volume > 255)
+ {
+ i32Volume = 255;
+ }
+
+ //
+ // Set the new volume.
+ //
+ g_sSoundState.i32Volume = i32Volume;
+}
+
+//*****************************************************************************
+//
+//! Decreases the volume of the sound playback.
+//!
+//! \param i32Volume is the amount by which to decrease the volume of the
+//! sound playback, specified as a value between 0 (for no adjustment) and 255
+//! maximum adjustment).
+//!
+//! This function decreases the volume of the sound playback relative to the
+//! current volume.
+//!
+//! \return None.
+//
+//*****************************************************************************
+void
+SoundVolumeDown(int32_t i32Volume)
+{
+ //
+ // Compute the new volume, limiting to the minimum if required.
+ //
+ i32Volume = g_sSoundState.i32Volume - i32Volume;
+ if(i32Volume < 0)
+ {
+ i32Volume = 0;
+ }
+
+ //
+ // Set the new volume.
+ //
+ g_sSoundState.i32Volume = i32Volume;
+}
+
+//*****************************************************************************
+//
+// Close the Doxygen group.
+//! @}
+//
+//*****************************************************************************