Звукова карта як послідовний порт

У сучасних ПК є проблема відсутності простих у використанні інтерфейсів. Для використання 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);
		}	
  }
}

<habracut/>
 
Код програми на ПК: <habracut/>
 
 
#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++;
       }
    }
  }
}

<habracut/>
 
На формі 2 кнопки «Stop» і «Run», а також поле з квадратів, положення яких за координатами х і y визначається амплітудою перших 2-х імпульсів, а яскравість амплітудою 3-го. Плати з'єднані телефонним дротом, з боку ПК стандартний jack для моно, з боку STM32VLDISCOVERY висновок PA.04, підключений через емітерний повторювач (в STM32VLDISCOVERY вихід ЦАП високоомний) і дільник для калібрування (змінний резистор).
 
 
 
Передане в ПК через мікрофонний вхід з STM32VLDISCOVERY тестове зображення (поле 32х32 квадрата з градієнтом яскравості квадратів зверху вниз):
 
 

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.