[Из песочницы] Звуковая карта как последовательный порт
В современных ПК есть проблема отсутствия простых в использовании интерфейсов. Для использования USB требуется большой объем непростого кода, а для UART нужен переходник USB-COM. Если внешнее устройство несложное, то разработка интерфейса может занять больше времени, чем разработка самого устройства. В то же время во многих устройствах есть аналоговый интерфейс для аудиоустройств, который можно использовать для ввода или вывода данных без какой бы то ни было доработки. Здесь пример ввода данных с платы STM32VLDISCOVERY в ПК с ОС Windows ХР через микрофонный вход. Интерфейс не чисто цифровой, а цифро-аналоговый. Данные с платы передаются пачками из 4-х прямоугольных импульсов разной амплитуды, через ЦАП контроллера. Частота следования импульсов соответствует верхней частоте входного усилителя большинства звуковых карт — 20 кГц. Начало пачки отмечается импульсом удвоенной ширины. Следующие 3 импульса несут информацию, которая заложена в амплитуде импульса. Скорость передачи данных при 4-х разрядном кодировании амплитуды составляет примерно 45 кбит/с.Код для прошивки STM32VLDISCOVERY:
#include «stm32f10x.h» #define DAC_DHR12RD_Address 0×40007420 #define BUF_SIZE 640 /* Init Structure definition */ DAC_InitTypeDef DAC_InitStructure; DMA_InitTypeDef DMA_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /* Private variables ---------------------------------------------------------*/ uint32_t DualSine12bit[BUF_SIZE], Idx = 0, Idx2 = 0, Idx3 = 0, a1, a2, a3, a4, cc; int RR; double R;
/* Private function prototypes -----------------------------------------------*/ void RCC_Configuration (void) { /* DMA1 clock enable */ RCC_AHBPeriphClockCmd (RCC_AHBPeriph_DMA1, ENABLE); /* GPIOA Periph clock enable */ RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA, ENABLE); /* DAC Periph clock enable */ RCC_APB1PeriphClockCmd (RCC_APB1Periph_DAC, ENABLE); /* TIM2 Periph clock enable */ RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2, ENABLE); }
void GPIO_Configuration (void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init (GPIOA, &GPIO_InitStructure); }
void Timebase_Configuration (void) { TIM_TimeBaseStructInit (&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Period = 0×120; //0×04; 0×150; TIM_TimeBaseStructure.TIM_Prescaler = 0×01; TIM_TimeBaseStructure.TIM_ClockDivision = 0×0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit (TIM2, &TIM_TimeBaseStructure); /* TIM2 TRGO selection */ TIM_SelectOutputTrigger (TIM2, TIM_TRGOSource_Update); /* TIM2 enable counter */ TIM_Cmd (TIM2, ENABLE); }
void DAC_Configuration () { /* DAC channel1 Configuration */ DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO; DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; //Enable; DAC_Init (DAC_Channel_1, &DAC_InitStructure); /* DAC channel2 Configuration */ DAC_Init (DAC_Channel_2, &DAC_InitStructure); /* Enable DAC Channel1: Once the DAC channel1 is enabled, PA.04 is automatically connected to the DAC converter. */ DAC_Cmd (DAC_Channel_1, ENABLE); /* Enable DAC Channel2: Once the DAC channel2 is enabled, PA.05 is automatically connected to the DAC converter. */ DAC_Cmd (DAC_Channel_2, ENABLE); /* Enable DMA for DAC Channel2 */ DAC_DMACmd (DAC_Channel_2, ENABLE); }
void DMA_Configuration () { /* DMA1 channel4 configuration */ DMA_DeInit (DMA1_Channel4); DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init (DMA1_Channel4, &DMA_InitStructure); /* Enable DMA1 Channel4 */ DMA_Cmd (DMA1_Channel4, ENABLE); }
void Delay (__IO uint32_t nCount) { for (; nCount!= 0; nCount--); }
void Point (uint32_t kx, uint32_t ky, uint32_t ki) { for (Idx2 = 0; Idx2 < BUF_SIZE-10; Idx2++) { DualSine12bit[Idx2+10] = DualSine12bit[Idx2]; } DualSine12bit[0] = 4095; DualSine12bit[1] = 4095; DualSine12bit[2] = 0; DualSine12bit[3] = 0; DualSine12bit[4] = 2096 + kx; DualSine12bit[5] = 2000 - kx; DualSine12bit[6] = 2096 + ky; DualSine12bit[7] = 2000 - ky; DualSine12bit[8] = 2096 + ki; DualSine12bit[9] = 2000 - ki; }
/* Private functions ---------------------------------------------------------*/ int main (void) { /* System Clocks Configuration */ RCC_Configuration ();
/* Once the DAC channel is enabled, the corresponding GPIO pin is automatically connected to the DAC converter. In order to avoid parasitic consumption, the GPIO pin should be configured in analog */ GPIO_Configuration ();
/* TIM2 Configuration */ /* Time base configuration */ Timebase_Configuration (); /* DAC channel1 Configuration */ DAC_Configuration (); /* DMA1 channel4 configuration */ DMA_Configuration ();
R = 1; RR=1; a1 = 2023; a2 = 1000; a3 = 100; a4 = 900; while (1) { for (Idx = 0; Idx < 32*32; Idx++) { Idx3 = Idx/32; Point((Idx-Idx3*32)*50,Idx3*50,Idx/32*50); } } }
Код приложения на ПК:
#include
TForm1 *Form1; static HWAVEIN hWaveIn = NULL; static WAVEHDR WaveHdr1, WaveHdr2; static WAVEFORMATEX waveformat; static unsigned short Buffer1[INP_BUFFER_SIZE], Buffer2[INP_BUFFER_SIZE], saveBuffer[INP_BUFFER_SIZE]; static signed int RR, saveBuffer2[INP_BUFFER_SIZE]; static BOOL bEnding, bGraph, flag; BOOL bShutOff; long int RR_max, RR_min, LLL; int ix, iy, iz, k, kx, ky, m, kp;
void CALLBACK waveInProc1(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) { switch (uMsg) { case WIM_OPEN: break;
case WIM_DATA: CopyMemory (saveBuffer, ((PWAVEHDR) dwParam1)→lpData, ((PWAVEHDR) dwParam1)→dwBytesRecorded) ; if (bEnding){ waveInReset (hWaveIn); waveInClose (hWaveIn); return; } waveInAddBuffer (hwi, (PWAVEHDR) dwParam1, sizeof (WAVEHDR)) ; // Send out a new buffer break;
case WIM_CLOSE: waveInUnprepareHeader (hWaveIn, &WaveHdr1, sizeof (WAVEHDR)) ; waveInUnprepareHeader (hWaveIn, &WaveHdr2, sizeof (WAVEHDR)) ; } }
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx void __fastcall TForm1:: startButtonClick (TObject *Sender) { bGraph=false; } //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx void __fastcall TForm1:: formDestroy (TObject *Sender) { bEnding=false; } //xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx void __fastcall TForm1:: Button1Click (TObject *Sender) { bGraph=true; }
//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx __fastcall TForm1:: TForm1(TComponent* Owner) : TForm (Owner) { waveformat.wFormatTag = WAVE_FORMAT_PCM; waveformat.nChannels = 1; //2; waveformat.wBitsPerSample = 16; waveformat.nSamplesPerSec = SAMPLE_RATE; waveformat.nBlockAlign = waveformat.nChannels * (waveformat.wBitsPerSample / 8); waveformat.nAvgBytesPerSec = waveformat.nBlockAlign * waveformat.nSamplesPerSec; waveformat.cbSize = 0;
if (waveInOpen (&hWaveIn, WAVE_MAPPER, &waveformat, (DWORD)waveInProc1, 0, CALLBACK_FUNCTION)){ Application→MessageBox (»000000000», «Error», MB_OK); return; }
bShutOff=false; // Set up headers and prepare them
WaveHdr1.lpData = (BYTE *)Buffer1; WaveHdr1.dwBufferLength = INP_BUFFER_SIZE*2; // WaveHdr1.dwBytesRecorded = 0; WaveHdr1.dwUser = 0; WaveHdr1.dwFlags = 0; WaveHdr1.dwLoops = 1; WaveHdr1.lpNext = NULL; WaveHdr1.reserved = 0; waveInPrepareHeader (hWaveIn, &WaveHdr1, sizeof (WAVEHDR)) ;
WaveHdr2.lpData = (BYTE *)Buffer2; WaveHdr2.dwBufferLength = INP_BUFFER_SIZE*2; // WaveHdr2.dwBytesRecorded = 0; WaveHdr2.dwUser = 0; WaveHdr2.dwFlags = 0; WaveHdr2.dwLoops = 1; WaveHdr2.lpNext = NULL; WaveHdr2.reserved = 0; waveInPrepareHeader (hWaveIn, &WaveHdr2, sizeof (WAVEHDR)) ;
waveInAddBuffer (hWaveIn, &WaveHdr1, sizeof (WAVEHDR)) ; waveInAddBuffer (hWaveIn, &WaveHdr2, sizeof (WAVEHDR)) ; waveInStart (hWaveIn) ;
bGraph=true; bEnding = FALSE; }
void __fastcall TForm1:: Timer1Timer (TObject *Sender)
{
if (bGraph){
kp++;
if (kp>20){kp=0; Canvas→Brush→Color = Color; Canvas→FillRect (Rect (0,0,512,512));}
k=0; m=0; RR_min=0; RR_max=0; kx=0; ky=0;
for (int LLL=0; LLL
if (RR < 0) { if(RR_min > RR) RR_min = RR; if (ky>6){ if (m==3) {Canvas→Brush→Color = TColor (RGB (iz, iz, iz)); Canvas→FillRect (Rect (ix, iy, ix+16, iy+16));} } if (! flag) m++; RR_max = 0; flag=true; ky=0; kx++; } } } }
На форме 2 кнопки «Stop» и «Run», а также поле из квадратов, положение которых по координатам х и y определяется амплитудой первых 2-х импульсов, а яркость амплитудой 3-го. Платы соединены телефонным проводом, со стороны ПК стандартный jack для моно, со стороны STM32VLDISCOVERY вывод PA.04, подключенный через эмиттерный повторитель (в STM32VLDISCOVERY выход ЦАП высокоомный) и делитель для калибровки (переменный резистор).
Переданное в ПК через микрофонный вход из STM32VLDISCOVERY тестовое изображение (поле 32×32 квадрата с градиентом яркости квадратов сверху вниз):