Урок 46
Часть 1
I2S AUDIO
Сегодня мы начинаем изучение нового для нас интерфейса – это шина I2S, чем то похожая на I2C, но заточенная непосредственно на передачу цифрового аудиопотока. Описание и диаграммы протокола данной шины можно найти на странице 894 STM32F4 Reference manual. Ну если кратко, данная шина является также синхронной, причем синхронизация обеспечивается не только для каждого переданного бита, как у I2C, а также поканально. То есть отдельный провод синхронизации задействован так, что при передачи полностью всех битов (их может быть 8, 16, 24) одного канала он находится в состоянии 1, а при передачи всех битов другого канала – в 0. Данное условие обеспечивает невозможность случайного обмена каналов между собой вследствие искажения сигнала потока.
Свой проект мы создадим из одного из предыдущих проектов USB_HOST_MSC_FATFS, так как работать мы будем с USB Flash Drive, ибо нам для изучения преобразования цифрового аудио надо это цифровое аудио откуда-то взять. Было принято решение взять его из WAV файлов, расположенных на данном носителе. Проект мы назовём по наименованию шины I2S_AUDIO. Так как мы ещё будем подключать к плате Discovery носитель USB Flash Drive, то необходимо туда скопировать для WAV-файла со звуком: Track1.wav и Track2.wav. Частота сэмплирования данных файлов может быть любая, но желательно не больше 48 кГц.
Посмотрим подключение аудио-микросхемы в плате Discovery (нажмите на картинку для увеличения изображения)
Данная микросхема именуется CS43L22. Основной её характеристикой является поддерживаемая частота дискретизации. Это от 4 кГц до 96 кГц. Откроем её даташит. Там существуют четыре вида протокола I2S. Мы используем самый первый стандарт. Единственное из даташита непонятно зачем нужен контакт MCLK. В даташите дано следующее описание данного контакта:
MCLK – Master Clock (Input) — Clock source for the delta-sigma modulators.
Пролистав несколько форумов, я нашел, что это ещё третий вид синхронизации – тактирование сэмплов.
Вот так у нас всё подключено к плате
Откроем наш проект в MS Cube и сделаем необходимое добавление определённых настроек. Включим первым делом саму шину I2S
Ножки никакие не переопределяем. Оставим так как есть. Именно по этим ножкам и подключена микросхема Аудио ЦАП.
Внесем также некоторые корректировки в настройки I2S
Для работы лапки RESET микросхемы нам будет ещё необходимо включить на выход и настроить данную лапку порта (PD4)
Также добавим и настроим DMA на шине I2S
А так как управление микросхемой происходит в отличии от основного аудио-потока уже по шине I2C, то необходимо включить ещё и эту шину
Только I2C3 мы отдадим под символьный дисплей, а I2C1 нам понадобится именно для аудио-микросхемы. Мало того, лапку PB7 нужно будет переопределить на PB9. Я думаю, все уже умеют это делать. Сначала сбросим её, а затем переопределим
Теперь переходник дисплея у нас будет подключен к другим лапкам портов:
SCL – PA8
SDA – PC9.
Сгенерируем проект для среды Keil, настроим программатор на авторезет, добавим в дерево проекта файл lcd.c и скомпилируем проект.
Создадим и добавим в проект новые файлы, предназначенные для работы со звуком, audioplay.c и audioplay.h следующего содержания:
audioplay.c:
#include «audioplay.h»
#include «fatfs.h»
#include «usb_host.h»
#include «lcd.h»
//——————————————————
audioplay.h:
#ifndef AUDIOPLAY_H_
#define AUDIOPLAY_H_
#ifdef __cplusplus
extern «C» {
#endif
//————————————————
#include «stm32f4xx_hal.h»
//————————————————
#define I2S3 SPI3
#ifdef __cplusplus
}
#endif
#endif /* AUDIOPLAY_H_ */
Также добавим подключение нашей библиотеки в главный модуль main.h
#include «lcd.h»
#include «audioplay.h»
Из-за того, что LCD-дисплей теперь находится на 3й шине I2C, то внесем некоторые изменения в файл lcd.c:
extern I2C_HandleTypeDef hi2c3;
…
HAL_I2C_Master_Transmit(&hi2c3,(uint16_t) 0x4E,buf,1,1000);
Уберем вот это из main()
char str[100];
/* USER CODE END 1 */
Также в main.c функцию FileReadWrite исправим в MenuProcess и очистим полностью её тело:
/* USER CODE BEGIN 0 */
void MenuProcess(void)
{
}
Соответственно, в бесконечном цикле также произойдут определённые изменения
while (1)
{
/* USER CODE END WHILE */
MX_USB_HOST_Process();
/* USER CODE BEGIN 3 */
MenuProcess();
}
/* USER CODE END 3 */
Добавим некоторое количество переменных и макроподстановок в заголовочный файл audioplay.h.
#define I2S3 SPI3
typedef enum
{
BUFFER_OFFSET_NONE = 0,
BUFFER_OFFSET_HALF,
BUFFER_OFFSET_FULL,
}BUFFER_StateTypeDef;
typedef struct
{
uint32_t ChunkID; /* 0 */
uint32_t FileSize; /* 4 */
uint32_t FileFormat; /* 8 */
uint32_t SubChunk1ID; /* 12 */
uint32_t SubChunk1Size; /* 16 */
uint16_t AudioFormat; /* 20 */
uint16_t NbrChannels; /* 22 */
uint32_t SampleRate; /* 24 */
uint32_t ByteRate; /* 28 */
uint16_t BlockAlign; /* 32 */
uint16_t BitPerSample; /* 34 */
uint32_t SubChunk2ID; /* 36 */
uint32_t SubChunk2Size; /* 40 */
}WAVE_FormatTypeDef;
/* Audio State Structure */
typedef enum {
AUDIO_IDLE = 0,
AUDIO_WAIT,
AUDIO_EXPLORE,
AUDIO_PLAYBACK,
AUDIO_IN,
}AUDIO_State;
/* Audio Demo State Machine Structure */
typedef struct _StateMachine {
__IO AUDIO_State state;
__IO uint8_t select;
}AUDIO_StateMachine;
//————————————————
И это ещё не всё. Также добавим ещё некоторое количество переменных, структур и макросов в файл audioplay.c
#include «lcd.h»
//——————————————————
#define I2S_STANDARD I2S_STANDARD_PHILIPS
/* Audio status definition */
#define AUDIO_OK 0
#define AUDIO_ERROR 1
#define AUDIO_TIMEOUT 2
/* Position in the audio play buffer */
__IO BUFFER_StateTypeDef buffer_offset = BUFFER_OFFSET_NONE;
/* Codec output DEVICE */
#define OUTPUT_DEVICE_SPEAKER 1
#define OUTPUT_DEVICE_HEADPHONE 2
#define OUTPUT_DEVICE_BOTH 3
#define OUTPUT_DEVICE_AUTO 4
/* MUTE commands */
#define AUDIO_MUTE_ON 1
#define AUDIO_MUTE_OFF 0
/* Defines for the Audio playing process */
#define PAUSE_STATUS ((uint32_t)0x00) /* Audio Player in Pause Status */
#define RESUME_STATUS ((uint32_t)0x01) /* Audio Player in Resume Status */
#define IDLE_STATUS ((uint32_t)0x02) /* Audio Player in Idle Status */
#define AUDIO_RESET_GPIO_CLK_ENABLE() __GPIOD_CLK_ENABLE()
#define AUDIO_RESET_PIN GPIO_PIN_4
#define AUDIO_RESET_GPIO GPIOD
#define VOLUME_CONVERT(Volume) (((Volume) > 100)? 100:((uint8_t)(((Volume) * 255) / 100)))
#define CODEC_STANDARD 0x04
/* Variables used in normal mode to manage audio file during DMA transfer */
uint32_t AudioTotalSize = 0xFFFF; /* This variable holds the total size of the audio file */
int32_t AudioRemSize = 0xFFFF; /* This variable holds the remaining data in audio file */
uint16_t *CurrentPos ; /* This variable holds the current position of audio pointer */
static uint8_t Is_cs43l22_Stop = 1;
__IO uint32_t PauseResumeStatus = IDLE_STATUS;
#define CS43L22_REG_MISC_CTL 0x0E
#define AUDIO_I2C_ADDRESS 0x94
#define CS43L22_CHIPID_ADDR 0x01
#define CS43L22_ID 0xE0
#define CS43L22_ID_MASK 0xF8
#define AUDIO_BUFFER_SIZE 0X8000
#define DMA_MAX_SZE 0xFFFF
#define DMA_MAX(_X_) (((_X_) <= DMA_MAX_SZE)? (_X_):DMA_MAX_SZE)
#define AUDIODATA_SIZE 2 /* 16-bits audio data size */
extern FIL WavFile;
const uint32_t I2SFreq[8] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 96000};
const uint32_t I2SPLLN[8] = {256, 429, 213, 429, 426, 271, 258, 344};
const uint32_t I2SPLLR[8] = {5, 4, 4, 4, 4, 6, 3, 1};
volatile uint8_t OutputDev = 0;
static uint32_t WaveDataLength = 0;
extern I2C_HandleTypeDef hi2c1;
extern I2S_HandleTypeDef hi2s3;
uint8_t Audio_Buffer[AUDIO_BUFFER_SIZE];
extern WAVE_FormatTypeDef *waveformat;
AUDIO_StateMachine Audio;
uint32_t samplerate;
extern ApplicationTypeDef Appli_state;
char str2[20];
char str3[20];
uint32_t offsetpos;
uint32_t cnt;
uint32_t dur; //переменная для оставшегося времени воспроизведения файла
//——————————————————
Вот сколько нам всего понадобится. Ну конечно, всё это не зря, это делается для нашего же удобство. Ну и чтобы не показалось неправильным extern без основного добавления переменной, добавим её в main.c, а также ещё несколько переменных, заменив здесь существующие, раз уж мы сюда попали
/* USER CODE BEGIN PV */
/* Private variables ———————————————————*/
FATFS USBDISKFatFs;
FIL WavFile;
extern char USBH_Path[4]; /* USBH logical drive path */
extern ApplicationTypeDef Appli_state;
extern AUDIO_StateMachine Audio;
WAVE_FormatTypeDef *waveformat = NULL;
uint32_t WaveDataLength = 0;
char str[100];
char FileName[100]={0};
uint8_t info[44];
/* USER CODE END PV */
Ну, наконец-то, мы можем приступить к основному коду. Сначала напишем функцию ошибки в файле audioplay.c. Такой функцией мы пользуемся постоянно
void Error(void)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);
}
//——————————————————
Напишем функцию инициализации
void AudioPlay_Init(uint32_t AudioFreq)
{
samplerate = AudioFreq;
__IO uint8_t volume = 70;
}
//————————————————-//————————————————-
Также создадим для данной функции прототип, так как она нам пригодится снаружи
}AUDIO_StateMachine;
//————————————————
void AudioPlay_Init(uint32_t AudioFreq);
//————————————————
В следующей части урока мы напишем функцию извлечения информации из звукового файла о параметрах данного файла и вывода этих параметров на дисплей.
Предыдущий урок Программирование МК STM32 Следующая часть
Техническая документация на Аудио ЦАП CS43L22
Отладочную плату и дисплей можно приобрести здесь:
Смотреть ВИДЕОУРОК
Доброго времени суток! Имеется цап СS4334, подключен к STM32F103RET6 по даташиту. Как мне загрузить туда синусоиду с типом данных uint24_t, если функция HAL_I2S_Trasmit_DMA принимает параметрами лишь 16 бит переменные???
Я работал с ЦАП-ом 12-бит, что встроен в МК. В том случае я делал воспроизведение синусоиды с типом данных uint16_t, но с амплитудой в 4095. Сейчас же понадобилось большее разрешение.
Извините, тип данных не uint24_t — такого просто нет, а uint32_t.
Было бы очень интересно узнать как передавать сигнал с ПК через USB на выходы i2s ЦАПа с помощью stm32
Доброго времени суток! Хочется выразить Вам огромную благодарность за Ваши непосильные труды. Ну и вопрос по теме данного урока — повторил все по Ваши материалам, Файлы читает, информация о аудио отображается корректно ,счетчик тикает НО в динамиках белый шум. В чем может быть дело? Спасибо