Pull to refresh

Звуковая карта как последовательный порт

Reading time 6 min
Views 17K
В современных ПК есть проблема отсутствия простых в использовании интерфейсов. Для использования USB требуется большой объем непростого кода, а для UART нужен переходник USB-COM. Если внешнее устройство несложное, то разработка интерфейса может занять больше времени, чем разработка самого устройства. В то же время во многих устройствах есть аналоговый интерфейс для аудиоустройств, который можно использовать для ввода или вывода данных без какой бы то ни было доработки. Здесь пример ввода данных с платы STM32VLDISCOVERY в ПК с ОС Windows ХР через микрофонный вход. Интерфейс не чисто цифровой, а цифро-аналоговый. Данные с платы передаются пачками из 4-х прямоугольных импульсов разной амплитуды, через ЦАП контроллера. Частота следования импульсов соответствует верхней частоте входного усилителя большинства звуковых карт – 20 кГц. Начало пачки отмечается импульсом удвоенной ширины. Следующие 3 импульса несут информацию, которая заложена в амплитуде импульса. Скорость передачи данных при 4-х разрядном кодировании амплитуды составляет примерно 45 кбит/с.

Код для прошивки STM32VLDISCOVERY:

#include "stm32f10x.h"
#define DAC_DHR12RD_Address      0x40007420
#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 = 0x120; //0x04;  0x150;         
  TIM_TimeBaseStructure.TIM_Prescaler = 0x01;       
  TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;    
  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 <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <fstream.h>
#include <iomanip.h>
#include <windows.h>
#include <math.h>
#include <vcl.h>
#include <mmsystem.h>
#include "mainF_dbl.h"
#pragma hdrstop
#pragma package(smart_init)
#pragma resource "*.dfm"
#define INP_BUFFER_SIZE 16384
#define SAMPLE_RATE     192000

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<INP_BUFFER_SIZE; LLL++)
   {
short)(saveBuffer[LL*2]);
     RR = (signed short)(saveBuffer[LLL]);
     if (RR > 0)
       {
        if(RR_max < RR) RR_max = RR;
        if((kx>6)&&(RR_min<30000))      { //&&(k==0)){
                                  m=0;
                                  }
        if(RR_min < 0) {
                        if (m==1) ix = -RR_min*16/1024;
                        if (m==2) iy = -RR_min*16/1024;
                        if (m==3) iz = -RR_min*4/512;
                       }
       flag=false;    kx=0;
       RR_min = 0; ky++;
       }

     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 квадрата с градиентом яркости квадратов сверху вниз):

Tags:
Hubs:
+34
Comments 15
Comments Comments 15

Articles