| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,322 @@ |
| 1 |
+/* |
|
| 2 |
+ * Author: Pawel Jablonski |
|
| 3 |
+ * E-mail: pj@xirx.net |
|
| 4 |
+ * WWW: xirx.net |
|
| 5 |
+ * GIT: git.xirx.net |
|
| 6 |
+ * |
|
| 7 |
+ * License: You can use this code however you like |
|
| 8 |
+ * but leave information about the original author. |
|
| 9 |
+ * Code is free for non-commercial and commercial use. |
|
| 10 |
+ */ |
|
| 11 |
+ |
|
| 12 |
+#include <stm32f0xx.h> |
|
| 13 |
+#include <stm32f0xx_hal.h> |
|
| 14 |
+#include <stm32f0xx_hal_gpio.h> |
|
| 15 |
+#include <stm32f0xx_hal_dac.h> |
|
| 16 |
+#include <stm32f0xx_hal_tim.h> |
|
| 17 |
+#include <stm32f0xx_hal_dma.h> |
|
| 18 |
+#include <stm32f0xx_hal_rcc.h> |
|
| 19 |
+#include <system_stm32f0xx.h> |
|
| 20 |
+ |
|
| 21 |
+#include "type.h" |
|
| 22 |
+ |
|
| 23 |
+#include "speaker.h" |
|
| 24 |
+ |
|
| 25 |
+#define SPEAKER_BUFFER_SIZE 32 //!< Note buffer size |
|
| 26 |
+#define SPEAKER_BUFFER_OUT_SIZE 16 //!< Output buffer size and square wave fill divider |
|
| 27 |
+ |
|
| 28 |
+#define SPEAKER_NOTE_QUANTITY 89 //!< Note quantity available to play |
|
| 29 |
+ |
|
| 30 |
+#define SPEAKER_TIME_MIN 1 //!< Minimum time to play for a note in ms multiplied by TIME_MUL |
|
| 31 |
+#define SPEAKER_TIME_MAX 250 //!< Maximum time to play for a note in ms multiplied by TIME_MUL |
|
| 32 |
+#define SPEAKER_TIME_MUL 10 //!< Time multiplicator for a note |
|
| 33 |
+ |
|
| 34 |
+#define SPEAKER_FILL_MIN 1 //!< Minimum fill of the square wave in percents divided by SPEAKER_BUFFER_OUT_SIZE |
|
| 35 |
+#define SPEAKER_FILL_MAX 15 //!< Maximum fill of the square wave in percents divided by SPEAKER_BUFFER_OUT_SIZE |
|
| 36 |
+ |
|
| 37 |
+#define SPEAKER_VOLUME_MAX 15.0 //!< Maximum volume level |
|
| 38 |
+ |
|
| 39 |
+#define SPEAKER_VALUE_ZERO 2048 //!< Output zero level for silence |
|
| 40 |
+#define SPEAKER_VALUE_MUL 2047.0 //!< Output level multiplicator used to conversion |
|
| 41 |
+#define SPEAKER_VALUE_VOLUME 0.1 //!< Output volume level multiplicator |
|
| 42 |
+ |
|
| 43 |
+//! List of frequencies for notes |
|
| 44 |
+static const unsigned short noteFrequency[SPEAKER_NOTE_QUANTITY] = {
|
|
| 45 |
+ 20, // Silent |
|
| 46 |
+ 27, 29, 31, // A0 - B0 |
|
| 47 |
+ 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, // C1 - B1 |
|
| 48 |
+ 65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123, // C2 - B2 |
|
| 49 |
+ 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, // C3 - B3 |
|
| 50 |
+ 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, // C4 - B4 |
|
| 51 |
+ 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, // C5 - B5 |
|
| 52 |
+ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, // C6 - B6 |
|
| 53 |
+ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, // C7 - B7 |
|
| 54 |
+ 4186 // C8 |
|
| 55 |
+}; |
|
| 56 |
+ |
|
| 57 |
+static volatile bool isPlaying = FALSE; //!< Playing status |
|
| 58 |
+ |
|
| 59 |
+static volatile unsigned int bufferStart = 0; //!< Data start position |
|
| 60 |
+static volatile unsigned int bufferEnd = 0; //!< Data end position |
|
| 61 |
+ |
|
| 62 |
+static DAC_HandleTypeDef dacHandle; //!< DAC HAL handle |
|
| 63 |
+static TIM_HandleTypeDef timHandle; //!< Timer HAL handle for playing square wave |
|
| 64 |
+static DAC_ChannelConfTypeDef channelConf; //!< Configuration of DAC HAL |
|
| 65 |
+static DMA_HandleTypeDef dmaHandle; //!< DMA HAL handle |
|
| 66 |
+static TIM_HandleTypeDef timNoteHandle; //!< Timer HAL handle for making a delay between notes |
|
| 67 |
+ |
|
| 68 |
+static struct SpeakerNote buffer[SPEAKER_BUFFER_SIZE]; //!< Circular note buffer |
|
| 69 |
+ |
|
| 70 |
+static uint16_t bufferOut[SPEAKER_BUFFER_OUT_SIZE]; //!< Output buffer |
|
| 71 |
+ |
|
| 72 |
+static void speakerPlay(unsigned int offset); |
|
| 73 |
+ |
|
| 74 |
+//! Init speaker and set up DMA, DAC, Timers |
|
| 75 |
+void speakerInit(void) |
|
| 76 |
+{
|
|
| 77 |
+ for(int i = 0; i < SPEAKER_BUFFER_OUT_SIZE; i++) |
|
| 78 |
+ {
|
|
| 79 |
+ bufferOut[i] = SPEAKER_VALUE_ZERO; |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ __HAL_RCC_GPIOA_CLK_ENABLE(); |
|
| 83 |
+ |
|
| 84 |
+ GPIO_InitTypeDef gpioInit = {0};
|
|
| 85 |
+ |
|
| 86 |
+ gpioInit.Pin = GPIO_PIN_4; |
|
| 87 |
+ gpioInit.Mode = GPIO_MODE_ANALOG; |
|
| 88 |
+ gpioInit.Pull = GPIO_NOPULL; |
|
| 89 |
+ gpioInit.Speed = GPIO_SPEED_FREQ_LOW; |
|
| 90 |
+ |
|
| 91 |
+ HAL_GPIO_Init(GPIOA, &gpioInit); |
|
| 92 |
+ |
|
| 93 |
+ __HAL_RCC_TIM2_CLK_ENABLE(); |
|
| 94 |
+ |
|
| 95 |
+ timHandle.Instance = TIM2; |
|
| 96 |
+ timHandle.Init.CounterMode = TIM_COUNTERMODE_UP; |
|
| 97 |
+ timHandle.Init.Prescaler = 0; |
|
| 98 |
+ timHandle.Init.Period = ((HAL_RCC_GetHCLKFreq() / 100000) - 1); |
|
| 99 |
+ timHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; |
|
| 100 |
+ timHandle.Init.RepetitionCounter = 0; |
|
| 101 |
+ |
|
| 102 |
+ HAL_TIM_Base_Init(&timHandle); |
|
| 103 |
+ HAL_TIM_Base_Start_IT(&timHandle); |
|
| 104 |
+ |
|
| 105 |
+ TIM_MasterConfigTypeDef sMasterConfig = {0};
|
|
| 106 |
+ |
|
| 107 |
+ sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; |
|
| 108 |
+ sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; |
|
| 109 |
+ HAL_TIMEx_MasterConfigSynchronization(&timHandle, &sMasterConfig); |
|
| 110 |
+ |
|
| 111 |
+ __HAL_RCC_DAC1_CLK_ENABLE(); |
|
| 112 |
+ |
|
| 113 |
+ dacHandle.Instance = DAC; |
|
| 114 |
+ |
|
| 115 |
+ HAL_DAC_Init(&dacHandle); |
|
| 116 |
+ |
|
| 117 |
+ channelConf.DAC_Trigger = DAC_TRIGGER_T2_TRGO; |
|
| 118 |
+ channelConf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; |
|
| 119 |
+ |
|
| 120 |
+ HAL_DAC_ConfigChannel(&dacHandle, &channelConf, DAC_CHANNEL_1); |
|
| 121 |
+ HAL_DAC_Start(&dacHandle, DAC_CHANNEL_1); |
|
| 122 |
+ |
|
| 123 |
+ __HAL_RCC_DMA1_CLK_ENABLE(); |
|
| 124 |
+ |
|
| 125 |
+ dmaHandle.Instance = DMA1_Channel3; |
|
| 126 |
+ dmaHandle.Init.Direction = DMA_MEMORY_TO_PERIPH; |
|
| 127 |
+ dmaHandle.Init.PeriphInc = DMA_PINC_DISABLE; |
|
| 128 |
+ dmaHandle.Init.MemInc = DMA_MINC_ENABLE; |
|
| 129 |
+ dmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; |
|
| 130 |
+ dmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; |
|
| 131 |
+ dmaHandle.Init.Mode = DMA_CIRCULAR; |
|
| 132 |
+ dmaHandle.Init.Priority = DMA_PRIORITY_HIGH; |
|
| 133 |
+ |
|
| 134 |
+ HAL_DMA_Init(&dmaHandle); |
|
| 135 |
+ |
|
| 136 |
+ __HAL_LINKDMA(&dacHandle, DMA_Handle1, dmaHandle); |
|
| 137 |
+ |
|
| 138 |
+ HAL_DAC_Start_DMA(&dacHandle, DAC_CHANNEL_1, (uint32_t *)bufferOut, SPEAKER_BUFFER_OUT_SIZE, DAC_ALIGN_12B_R); |
|
| 139 |
+ |
|
| 140 |
+ __HAL_RCC_TIM6_CLK_ENABLE(); |
|
| 141 |
+ |
|
| 142 |
+ timNoteHandle.Instance = TIM6; |
|
| 143 |
+ timNoteHandle.Init.CounterMode = TIM_COUNTERMODE_UP; |
|
| 144 |
+ timNoteHandle.Init.Prescaler = ((HAL_RCC_GetHCLKFreq() / 1000) - 1); |
|
| 145 |
+ timNoteHandle.Init.Period = 0; |
|
| 146 |
+ timNoteHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; |
|
| 147 |
+ timNoteHandle.Init.RepetitionCounter = 0; |
|
| 148 |
+ |
|
| 149 |
+ HAL_TIM_Base_Init(&timNoteHandle); |
|
| 150 |
+ |
|
| 151 |
+ HAL_NVIC_SetPriority(TIM6_IRQn, 3, 0); |
|
| 152 |
+ HAL_NVIC_EnableIRQ(TIM6_IRQn); |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+//! Clear the speaker buffers and stop playing sound |
|
| 156 |
+void speakerClear(void) |
|
| 157 |
+{
|
|
| 158 |
+ __disable_irq(); |
|
| 159 |
+ {
|
|
| 160 |
+ isPlaying = FALSE; |
|
| 161 |
+ |
|
| 162 |
+ bufferStart = 0; |
|
| 163 |
+ bufferEnd = 0; |
|
| 164 |
+ |
|
| 165 |
+ HAL_TIM_Base_Stop_IT(&timNoteHandle); |
|
| 166 |
+ |
|
| 167 |
+ for(int i = 0; i < SPEAKER_BUFFER_OUT_SIZE; i++) |
|
| 168 |
+ {
|
|
| 169 |
+ bufferOut[i] = SPEAKER_VALUE_ZERO; |
|
| 170 |
+ } |
|
| 171 |
+ } |
|
| 172 |
+ __enable_irq(); |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+/** |
|
| 176 |
+ * Add note to the buffer |
|
| 177 |
+ * |
|
| 178 |
+ * @param note Note to add to the buffer |
|
| 179 |
+ */ |
|
| 180 |
+void speakerAddNote(struct SpeakerNote note) |
|
| 181 |
+{
|
|
| 182 |
+ if(note.note >= SPEAKER_NOTE_QUANTITY) |
|
| 183 |
+ {
|
|
| 184 |
+ return; |
|
| 185 |
+ } |
|
| 186 |
+ |
|
| 187 |
+ if((note.time < SPEAKER_TIME_MIN) || (note.time > SPEAKER_TIME_MAX)) |
|
| 188 |
+ {
|
|
| 189 |
+ return; |
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ if(note.note != NOTE_SILENT) |
|
| 193 |
+ {
|
|
| 194 |
+ if((note.fill < SPEAKER_FILL_MIN) || (note.fill > SPEAKER_FILL_MAX)) |
|
| 195 |
+ {
|
|
| 196 |
+ return; |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 199 |
+ if(note.volume > SPEAKER_VOLUME_MAX) |
|
| 200 |
+ {
|
|
| 201 |
+ return; |
|
| 202 |
+ } |
|
| 203 |
+ } |
|
| 204 |
+ |
|
| 205 |
+ __disable_irq(); |
|
| 206 |
+ {
|
|
| 207 |
+ unsigned int last = bufferEnd; |
|
| 208 |
+ |
|
| 209 |
+ if(bufferStart == ((last + 1) % SPEAKER_BUFFER_SIZE)) |
|
| 210 |
+ {
|
|
| 211 |
+ __enable_irq(); |
|
| 212 |
+ return; |
|
| 213 |
+ } |
|
| 214 |
+ |
|
| 215 |
+ buffer[last].note = note.note; |
|
| 216 |
+ buffer[last].time = note.time; |
|
| 217 |
+ |
|
| 218 |
+ if(note.note == NOTE_SILENT) |
|
| 219 |
+ {
|
|
| 220 |
+ buffer[last].fill = SPEAKER_FILL_MIN; |
|
| 221 |
+ buffer[last].volume = 0; |
|
| 222 |
+ } |
|
| 223 |
+ else |
|
| 224 |
+ {
|
|
| 225 |
+ buffer[last].fill = note.fill; |
|
| 226 |
+ buffer[last].volume = note.volume; |
|
| 227 |
+ } |
|
| 228 |
+ |
|
| 229 |
+ bufferEnd = ((last + 1) % SPEAKER_BUFFER_SIZE); |
|
| 230 |
+ |
|
| 231 |
+ if(!isPlaying) |
|
| 232 |
+ {
|
|
| 233 |
+ __enable_irq(); |
|
| 234 |
+ |
|
| 235 |
+ speakerPlay(bufferStart); |
|
| 236 |
+ |
|
| 237 |
+ return; |
|
| 238 |
+ } |
|
| 239 |
+ } |
|
| 240 |
+ __enable_irq(); |
|
| 241 |
+ |
|
| 242 |
+ return; |
|
| 243 |
+} |
|
| 244 |
+ |
|
| 245 |
+/** |
|
| 246 |
+ * Get free space size in the buffer |
|
| 247 |
+ * |
|
| 248 |
+ * @return Free space size in the buffer |
|
| 249 |
+ */ |
|
| 250 |
+unsigned char speakerGetBufferFree(void) |
|
| 251 |
+{
|
|
| 252 |
+ unsigned char bufferFree = (SPEAKER_BUFFER_SIZE - 1); |
|
| 253 |
+ |
|
| 254 |
+ __disable_irq(); |
|
| 255 |
+ {
|
|
| 256 |
+ if(bufferStart < bufferEnd) |
|
| 257 |
+ {
|
|
| 258 |
+ bufferFree -= (bufferEnd - bufferStart); |
|
| 259 |
+ } |
|
| 260 |
+ else if(bufferStart > bufferEnd) |
|
| 261 |
+ {
|
|
| 262 |
+ bufferStart = ((bufferStart - bufferEnd) - 1); |
|
| 263 |
+ } |
|
| 264 |
+ } |
|
| 265 |
+ __enable_irq(); |
|
| 266 |
+ |
|
| 267 |
+ return(bufferFree); |
|
| 268 |
+} |
|
| 269 |
+ |
|
| 270 |
+/** |
|
| 271 |
+ * Play a note |
|
| 272 |
+ * |
|
| 273 |
+ * @param offset Position of the note in the note buffer |
|
| 274 |
+ */ |
|
| 275 |
+static void speakerPlay(unsigned int offset) |
|
| 276 |
+{
|
|
| 277 |
+ __disable_irq(); |
|
| 278 |
+ {
|
|
| 279 |
+ uint16_t signalLevel = (uint16_t)(SPEAKER_VALUE_MUL * SPEAKER_VALUE_VOLUME * ((float)(buffer[offset].volume) / SPEAKER_VOLUME_MAX)); |
|
| 280 |
+ |
|
| 281 |
+ uint16_t high = (SPEAKER_VALUE_ZERO + signalLevel); |
|
| 282 |
+ uint16_t low = (SPEAKER_VALUE_ZERO - signalLevel); |
|
| 283 |
+ |
|
| 284 |
+ for(int i = 0; i < SPEAKER_BUFFER_OUT_SIZE; i++) |
|
| 285 |
+ {
|
|
| 286 |
+ bufferOut[i] = ((i < buffer[offset].fill) ? high : low); |
|
| 287 |
+ } |
|
| 288 |
+ |
|
| 289 |
+ __HAL_TIM_SET_AUTORELOAD(&timHandle, ((HAL_RCC_GetHCLKFreq() / (noteFrequency[buffer[offset].note] * SPEAKER_BUFFER_OUT_SIZE)) - 1)); |
|
| 290 |
+ __HAL_TIM_SET_COUNTER(&timHandle, 0); |
|
| 291 |
+ |
|
| 292 |
+ __HAL_TIM_SET_AUTORELOAD(&timNoteHandle, (((uint32_t)(buffer[offset].time) * SPEAKER_TIME_MUL) - 1)); |
|
| 293 |
+ __HAL_TIM_SET_COUNTER(&timNoteHandle, 0); |
|
| 294 |
+ |
|
| 295 |
+ if(!isPlaying) |
|
| 296 |
+ {
|
|
| 297 |
+ isPlaying = TRUE; |
|
| 298 |
+ |
|
| 299 |
+ HAL_TIM_Base_Start_IT(&timNoteHandle); |
|
| 300 |
+ } |
|
| 301 |
+ } |
|
| 302 |
+ __enable_irq(); |
|
| 303 |
+} |
|
| 304 |
+ |
|
| 305 |
+//! Load and play a next note from the note buffer. It is an IRQ handler connected to the timer HAL handle for making a delay between notes. |
|
| 306 |
+void TIM6_IRQHandler(void) |
|
| 307 |
+{
|
|
| 308 |
+ if(bufferStart == bufferEnd) |
|
| 309 |
+ {
|
|
| 310 |
+ speakerClear(); |
|
| 311 |
+ } |
|
| 312 |
+ else |
|
| 313 |
+ {
|
|
| 314 |
+ unsigned int first = bufferStart; |
|
| 315 |
+ |
|
| 316 |
+ speakerPlay(first); |
|
| 317 |
+ |
|
| 318 |
+ bufferStart = ((first + 1) % SPEAKER_BUFFER_SIZE); |
|
| 319 |
+ } |
|
| 320 |
+ |
|
| 321 |
+ HAL_TIM_IRQHandler(&timNoteHandle); |
|
| 322 |
+} |