Mühendislik ve hobi elektroniğinin en keyifli noktalarından biri, günlük hayatta kullandığımız cihazları sıfırdan kendi ellerimizle üretmektir. Bu projelerin içinde hem gömülü yazılım hem de yüksek kaliteli multimedya deneyimini bir arada sunan en popüler işlerden biri: Kendi USB Ses Kartınızı (DAC) yapmaktır.
Bu rehberimizde, fiyat/performans oranıyla gömülü sistemlerin vazgeçilmezi olan STM32F401 mikrodenetleyicisini ve muhteşem ses netliği sunan popüler PCM5102 I2S DAC modülünü kullanarak bilgisayarınız için taşınabilir bir ses kartı tasarlayacağız. İnternetteki karmaşık anlatımların aksine, işin mantığını ve donanım mimarisini adım adım ele alıyoruz.
Projemizin temelinde, ses verilerini mikrodenetleyiciden harici bir kodlayıcıya (DAC) kayıpsız aktarmak için geliştirilmiş olan I2S (Inter-IC Sound) protokolü yer alıyor. STM32F401 ile PCM5102 modülü arasındaki elektriksel bağları kurarken şu pin haritasını takip etmemiz gerekiyor:
| PCM5102 DAC Pinleri | STM32F401 (I2S2) Pinleri | Açıklama |
|---|---|---|
| VCC | 3.3V | Pozitif Besleme Hattı |
| GND | GND | Şasi / Ortak Toprak |
| BCK | PB13 (I2S2_CK) | Bit Clock (Veri Akış Saati) |
| DIN | PB15 (I2S2_SD) | Data In (Ham Dijital Ses Verisi) |
| LRCK | PB12 (I2S2_WS) | Left/Right Clock (Kanal Seçim Sinyali) |
| SCK | GND | Dahili Sistem Saati (GND'ye çekildiğinde modül kendi saatini üretir) |
Bağlantıları tamamladıktan sonra STM32’nin yapılandırma aracı olan CubeMX üzerinden şu üç kritik ayağı aktif hale getiriyoruz:
16-Bit / 48000 Hz olarak belirliyoruz.48000 Hz yapıyoruz. Ses verisinin işlemciyi yormadan doğrudan akması için de DMA (Direct Memory Access) ayarını Circular (Dairesel) modda ekliyoruz.Profesyonel Not: Sistem saat hızı (Clock Configuration) ayarlanırken, I2S birimine giden frekansın tam 48kHz katlarına denk gelmesine özen gösterilmelidir. CubeMX ekranındaki "Real Audio Frequency" hata payının %0.01 gibi minimum değerlerde olması, ses kalitesinin kusursuz olmasını sağlar.
Kod yapısı üretildiğinde, projenin tüm kontrolü usbd_audio_if.c dosyasına devredilir. Bilgisayardan gelen ham ses paketleri, her milisaniyede bir mikrodenetleyicinin dairesel bellek buffer'ına akar. Bizim yapmamız gereken tek şey, donanım başladığı an DMA motoruna ilk hareketi vermek ve gelen bu verileri I2S hattına pürüzsüzce beslemektir.
Ayrıca, bu aşamada dijital ses verileri elimizden ham olarak geçtiği için, ses seviyesini bozmadan ve çatlatmadan yazılımsal olarak artırmak veya filtrelemek gibi sinyal işleme (DSP) adımlarını da aynı fonksiyonlar içerisinde gerçekleştirebiliriz.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : usbd_audio_if.c
* @version : v1.0_Cube
* @brief : Generic media access layer.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usbd_audio_if.h"
/* USER CODE BEGIN INCLUDE */
/* USER CODE END INCLUDE */
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
/* USER CODE END PV */
/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
* @brief Usb device library.
* @{
*/
/** @addtogroup USBD_AUDIO_IF
* @{
*/
/** @defgroup USBD_AUDIO_IF_Private_TypesDefinitions USBD_AUDIO_IF_Private_TypesDefinitions
* @brief Private types.
* @{
*/
/* USER CODE BEGIN PRIVATE_TYPES */
extern I2S_HandleTypeDef hi2s2; // CubeMX'te hangi I2S'i seçtiysen (hi2s1, hi2s2 vs.)
// Ses ara belleği (USB kütüphanesi bunu otomatik yönetir ama bizim de izlememiz gerekir)
extern USBD_HandleTypeDef hUsbDeviceFS;
volatile uint8_t Audio_State = 0; // 0: Stop, 1: Play
/* USER CODE END PRIVATE_TYPES */
/**
* @}
*/
/** @defgroup USBD_AUDIO_IF_Private_Defines USBD_AUDIO_IF_Private_Defines
* @brief Private defines.
* @{
*/
/* USER CODE BEGIN PRIVATE_DEFINES */
/* USER CODE END PRIVATE_DEFINES */
/**
* @}
*/
/** @defgroup USBD_AUDIO_IF_Private_Macros USBD_AUDIO_IF_Private_Macros
* @brief Private macros.
* @{
*/
/* USER CODE BEGIN PRIVATE_MACRO */
/* USER CODE END PRIVATE_MACRO */
/**
* @}
*/
/** @defgroup USBD_AUDIO_IF_Private_Variables USBD_AUDIO_IF_Private_Variables
* @brief Private variables.
* @{
*/
/* USER CODE BEGIN PRIVATE_VARIABLES */
/* USER CODE END PRIVATE_VARIABLES */
/**
* @}
*/
/** @defgroup USBD_AUDIO_IF_Exported_Variables USBD_AUDIO_IF_Exported_Variables
* @brief Public variables.
* @{
*/
extern USBD_HandleTypeDef hUsbDeviceFS;
/* USER CODE BEGIN EXPORTED_VARIABLES */
/* USER CODE END EXPORTED_VARIABLES */
/**
* @}
*/
/** @defgroup USBD_AUDIO_IF_Private_FunctionPrototypes USBD_AUDIO_IF_Private_FunctionPrototypes
* @brief Private functions declaration.
* @{
*/
static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options);
static int8_t AUDIO_DeInit_FS(uint32_t options);
static int8_t AUDIO_AudioCmd_FS(uint8_t* pbuf, uint32_t size, uint8_t cmd);
static int8_t AUDIO_VolumeCtl_FS(uint8_t vol);
static int8_t AUDIO_MuteCtl_FS(uint8_t cmd);
static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd);
static int8_t AUDIO_GetState_FS(void);
/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */
/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */
/**
* @}
*/
USBD_AUDIO_ItfTypeDef USBD_AUDIO_fops_FS =
{
AUDIO_Init_FS,
AUDIO_DeInit_FS,
AUDIO_AudioCmd_FS,
AUDIO_VolumeCtl_FS,
AUDIO_MuteCtl_FS,
AUDIO_PeriodicTC_FS,
AUDIO_GetState_FS,
};
/* Private functions ---------------------------------------------------------*/
/**
* @brief Initializes the AUDIO media low layer over USB FS IP
* @param AudioFreq: Audio frequency used to play the audio stream.
* @param Volume: Initial volume level (from 0 (Mute) to 100 (Max))
* @param options: Reserved for future use
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options)
{
/* USER CODE BEGIN 0 */
UNUSED(AudioFreq);
UNUSED(Volume);
UNUSED(options);
return (USBD_OK);
/* USER CODE END 0 */
}
/**
* @brief De-Initializes the AUDIO media low layer
* @param options: Reserved for future use
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t AUDIO_DeInit_FS(uint32_t options)
{
/* USER CODE BEGIN 1 */
UNUSED(options);
return (USBD_OK);
/* USER CODE END 1 */
}
/**
* @brief Handles AUDIO command.
* @param pbuf: Pointer to buffer of data to be sent
* @param size: Number of data to be sent (in bytes)
* @param cmd: Command opcode
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t AUDIO_AudioCmd_FS(uint8_t* pbuf, uint32_t size, uint8_t cmd)
{
/* USER CODE BEGIN 2 */
switch(cmd)
{
case AUDIO_CMD_START:
if(HAL_I2S_GetState(&hi2s2) == HAL_I2S_STATE_READY)
{
// ÖNEMLİ: size / 2 yerine doğrudan kütüphanenin belirlediği toplam boyutu
// Half-Word (16-bit) birim sayısına bölerek başlatıyoruz.
// Eğer AUDIO_TOTAL_BUF_SIZE kullanıyorsanız onu 2'ye bölerek eleman sayısını verin.
if(HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t*)pbuf, AUDIO_TOTAL_BUF_SIZE / 2) == HAL_OK)
{
Audio_State = 1;
}
}
break;
case AUDIO_CMD_PLAY:
if(hi2s2.State == HAL_I2S_STATE_BUSY_TX && Audio_State == 0)
{
HAL_I2S_DMAResume(&hi2s2);
Audio_State = 1;
}
break;
case AUDIO_CMD_STOP:
HAL_I2S_DMAStop(&hi2s2);
Audio_State = 0;
break;
}
return (USBD_OK);
/* USER CODE END 2 */
}
/**
* @brief Controls AUDIO Volume.
* @param vol: volume level (0..100)
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t AUDIO_VolumeCtl_FS(uint8_t vol)
{
/* USER CODE BEGIN 3 */
UNUSED(vol);
return (USBD_OK);
/* USER CODE END 3 */
}
/**
* @brief Controls AUDIO Mute.
* @param cmd: command opcode
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t AUDIO_MuteCtl_FS(uint8_t cmd)
{
/* USER CODE BEGIN 4 */
UNUSED(cmd);
return (USBD_OK);
/* USER CODE END 4 */
}
/**
* @brief AUDIO_PeriodicT_FS
* @param cmd: Command opcode
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd)
{
/* USER CODE BEGIN 5 */
if (pbuf != NULL && size > 0)
{
int16_t *samples = (int16_t*)pbuf;
uint32_t total_samples = size / 2;
for (uint32_t i = 0; i < total_samples; i++)
{
/* FLOAT KULLANMIYORUZ!
Sesi ~1.5 kat artırmak için: (Veri * 3) / 2 yapıyoruz.
Sesi ~1.75 kat artırmak için: (Veri * 7) / 4 yapabilirsiniz.
Burada (samples[i] * 3) >> 1 ifadesi hem çarpıp ikiye böler,
hem de işlemci için sadece bit kaydırmadır (aşırı hızlıdır).
*/
int32_t amplified = (int32_t)(samples[i] * 4) >> 1;
// Sınır kontrolü (Satürasyon) - Robotlaşmayı ve patlamayı önler
if (amplified > 32767) amplified = 32767;
else if (amplified < -32768) amplified = -32768;
samples[i] = (int16_t)amplified;
}
}
return (USBD_OK);
/* USER CODE END 5 */
}
/**
* @brief Gets AUDIO State.
* @retval USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t AUDIO_GetState_FS(void)
{
/* USER CODE BEGIN 6 */
return (USBD_OK);
/* USER CODE END 6 */
}
/**
* @brief Manages the DMA full transfer complete event.
* @retval None
*/
void TransferComplete_CallBack_FS(void)
{
/* USER CODE BEGIN 7 */
USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_FULL);
/* USER CODE END 7 */
}
/**
* @brief Manages the DMA Half transfer complete event.
* @retval None
*/
void HalfTransfer_CallBack_FS(void)
{
/* USER CODE BEGIN 8 */
USBD_AUDIO_Sync(&hUsbDeviceFS, AUDIO_OFFSET_HALF);
/* USER CODE END 8 */
}
/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */
/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */
/**
* @}
*/
/**
* @}
*/
Projenizi bilgisayara takıp kulaklığınızı bağladığınızda, kendi yaptığınız bir donanımdan stüdyo kalitesinde (16-bit / 48kHz) ses duymanın keyfi paha biçilemez. Ancak bu tarz senkron USB ses projelerinde, işletim sisteminin (Windows/Linux) paket gönderme hızlarındaki anlık dalgalanmalar veya programların ses kapatma refleksleri, bazen dairesel bellek yapılarında kararsızlıklara yol açabilir.
Eğer projenizi bir adım daha ileriye taşıyıp ticari bir ürün kalitesine getirmek isterseniz şu iki yöntemi araştırabilirsiniz:
XSMT pinini aktif kullanarak, bilgisayardan ses sinyali gelmediği anlarda DAC çipinin kendisini otomatik olarak donanımsal sessizliğe (Mute) gömmesini sağlayabilirsiniz.Kendi USB ses kartınızı yapmak, hem USB protokolünü hem de dijital ses mimarisini öğrenmek için harika bir giriş projesidir. Tasarım yapacak herkese şimdiden iyi çalışmalar!
Henüz yorum yapılmamış. İlk yorumu siz yapın!