Skip to content
lee jaejung edited this page May 2, 2015 · 7 revisions

이 글은 Stm32 Value line discovery 보드 전용 글이다.

시리얼통신을 처리하는 방법은 크게 세가지로 나눌 수 있다. 하나는 플래그 값을 확인하면서 while문으로 프로그래밍하는 방법이 있고(흔히 폴링이라고 한다), 두 번째는 인터럽트를 설정해 정보를 주고받을 때마다 인터럽트에서 처리하는 방법이 있다. 그리고 마지막으로 이 글에서 설명할 DMA방식이 있다. 이 방법은 임시메모리를 만들어 시리얼통신으로 주고 받는 정보들을 메모리로 관리하는 방식이다. 형식이 있는 많은 종류의 정보들을 주고 받을려면 이러한 DMA 방식이 효율적이다.

소스코드는 STM32\stm32vldiscovery_package\Project\Examples\Serial_DMA 폴더에 있다. 이 예제는 USART1 PA10,9(Rx,Tx) 포트를 이용해서 시리얼 통신을 한다. 먼저 특정 문자열을 송신하고, 일정이상의 갯수만큼 입력을 받아, 받은 문자를 그대로 송신한다. 이렇게 계속 반복한다.

DMA(Direct Memory Access) CPU를 거치지않고, 자체 컨트롤러에서 처리하기 때문에 속도가 빠르다.

I2C통신이나 DAC, ADC 모듈에서도 DMA를 사용한다. 일반적으로 여러 정보를 하나의 모듈에서 처리할 때 DMA를 사용한다. 시리얼 통신 또한 마찬가지다. Rx,Tx로 주고받는 정보들을 버퍼에 담아 처리해야하기 때문이다.

DMA에 대한 자세한 내용은 매뉴얼을 참고하자. 블럭다이어그램과 레지스터 정보를 보여주는데, 하드웨어에 능통한 사람이 아니라면 이해하기 쉽지않은 설명들이다. 일단 코드를 보면서 설명을 하겠다.

Stm32F10x 계열의 MCU는 USART1, USART2 두 개의 USART모듈이 있다. USART1의 포트는 일반적으로 PA10,9 (Rx,Tx)로 한다. USART2의 포트는 PA3,2(Rx,Tx) 다. remapping하면 포트를 바꿀 수 있다.

GPIO 클럭을 초기화하고, 포트 속성을 설정하는 것은 일반적인 USART설정과 다를바 없다. Stm32 UART문서에 나와 있다. USART에서 DMA를 사용하려면 추가적으로 USART_DMACmd()함수와 DMA_Cmd()함수를 사용한다.

GPIO 초기화

USART1을 초기화 한다. PA10,9(Rx,Tx)포트를 설정하고, 클럭을 설정한다. DMA를 사용할 것이니 DMA클럭도 설정한다.

void main()
{
  /* System Clocks Configuration */
  RCC_Configuration();
 
  /* Configure the GPIO ports */
  GPIO_Configuration();

  /* Configure the DMA */
  DMA_Configuration();	
}

void RCC_Configuration(void)
{
  // DMA clock enable
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

  // Enable GPIO clock
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
}

void GPIO_Configuration(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
	
  /* Configure USART1 Rx as input floating */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOA, &GPIO_InitStructure);  
  
  /* Configure USART1 Tx as alternate function push-pull */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
}

DMA 초기화

DMA를 초기화 한다. DMA1_Channel4 채널을 Tx로 DMA1_Channel5를 Rx로 설정한다. 이 채널은 이미 정해져 있으니 다른것을 쓸 수 없다. 다시말하면 USART1_TX 는 DMA1_Channel4 채널이어야 하고, USART1_RX는 DMA1_Channel5 로 정해져 있다.

DMA1_Channel5의 DMA_Mode가 DMA_Mode_Circular인 것에 주목하자. 정보가 버퍼를 넘어가도 처음으로 다시 돌아오니, 주기적으로 버퍼를 가져오면 문제없이 시리얼 통신을 할 수 있다. 나머지 플래그들은 매뉴얼을 참조하자.

DMA1_Channel4는 쓰기버퍼가 되며, TxBuffer1[]의 정보를 USART1->DR 레지스터에 주기적으로 쓰는 역할을 한다. 이렇게되면 TxBuffer1[]저장된 정보가 시리얼을 통해 송신되게 된다. DMA1_Channel5는 읽기버퍼가 되며, USART1->DR 레지스터의 정보를 읽어 RxBuffer1[]에 주기적으로 쓰는 역할을 한다. 이렇게되면 시리얼통신으로 넘어온 정보가 RxBuffer1[]에 저장되게 된다. 통신의 마무리는 TxBufferSize1만큼 쓰거나(DMA1_Channel4), 받으면(DMA1_Channel5) 종료되게 된다.

#define TxBufferSize1   (countof(TxBuffer1) - 1)
#define countof(a)   (sizeof(a) / sizeof(*(a)))

uint8_t TxBuffer1[] = "USART DMA Interrupt: USARTy -> USARTz using DMA Tx and Rx Flag";
uint8_t RxBuffer1[TxBufferSize1];
uint8_t NbrOfDataToRead = TxBufferSize1;

void DMA_Configuration(void)
{
  DMA_InitTypeDef DMA_InitStructure;

  // USARTy_Tx_DMA_Channel (triggered by USARTy Tx event) Config
  DMA_DeInit(DMA1_Channel4);	
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)TxBuffer1;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
  DMA_InitStructure.DMA_BufferSize = TxBufferSize1;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  DMA_Init(DMA1_Channel4, &DMA_InitStructure);
  
  // USARTy_Rx_DMA_Channel (triggered by USARTy Rx event) Config
  DMA_DeInit(DMA1_Channel5);
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer1;
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  DMA_InitStructure.DMA_BufferSize = TxBufferSize1;
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	
  DMA_Init(DMA1_Channel5, &DMA_InitStructure);
}

USART 초기화

USART1 옵션을 설정하고, 활성화 한다. USART_DMACmd()함수와 DMA_Cmd()함수로 DMA를 설정하고, 활성화 한다.

DMA_Cmd(DMA1_Channel4, ENABLE);가 실행되는 순간 시리얼 송신을 시작한다.

  // USART1 초기화	
  USART_InitTypeDef USART_InitStructure;
  USART_InitStructure.USART_BaudRate = 9600;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	
	
  USART_Init(USART1, &USART_InitStructure);
	
  // Enable USART1 DMA TX request
  USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE);

  // Enable USART1
  USART_Cmd(USART1, ENABLE);

  // Enable USART1 DMA TX, RX Channel
  DMA_Cmd(DMA1_Channel4, ENABLE);
  DMA_Cmd(DMA1_Channel5, ENABLE);

매인루프

TxBuffer1[] 정보를 시리얼 송신하고, RxBuffer1[]에 정보를 저장하고 있다. DMA_GetFlagStatus()함수는 DMA 상태를 얻어오는 함수다. DMA_GetFlagStatus(DMA1_FLAG_TC4) 코드는 DMA1_Channel4가 메모리를 모두 복사했는지를 알아보는 함수다. TC는 transmit complete의 약자다. DMA_GetFlagStatus(DMA1_FLAG_TC5)코드도 마찬가지다.

송수신이 모두 끝났으면, 받은 정보가 RxBuffer1에 저장되어 있을테니, 그 정보를 그대로 TxBuffer1에 저장하고, 다시 송신한다.

주의해야 될 점은 DMA가 자기역할이 끝나면 정보가 변경되어 있다는 점이다. 일단 송수신 크기 값이 바뀐 상태이고, baseAddress도 바뀐상태다. 이 값을 원래대로 복구한 다음 DMA_Cmd()함수로 재활성화 하면된다.

그리고 DMA transfer를 진행하면서 여러 플래그가 설정되었던 것을 DMA_ClearITPendingBit()함수로 초기화한다.

while(1)
{
   int i=0;
   while (DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET)
   {
   }

	// Wait until USARTy RX DMA1 Channel Transfer Complete 
	while (DMA_GetFlagStatus(DMA1_FLAG_TC5) == RESET)
	{
	}

	// TxBufferSize1 길이 만큼 시리얼 입력을 받은 후에 똑같은 문자열을 다시 보낸다.
	// 그리고, 다시 시리얼입력을 들어 올 때 까지 대기한다.
		
	for (i=0; i < TxBufferSize1; ++i)
	{
		TxBuffer1[ i] = RxBuffer1[ i];
	}

		
    DMA_ClearITPendingBit(DMA1_IT_TC4);
    DMA_ClearITPendingBit(DMA1_IT_TC5);
    DMA_Cmd(DMA1_Channel4, DISABLE);
		
    uartStartTxDMA(DMA1_Channel4);
}

void uartStartTxDMA(DMA_Channel_TypeDef *txDMAChannel)
{
    txDMAChannel->CMAR = (uint32_t)TxBuffer1;
    txDMAChannel->CNDTR = TxBufferSize1;
    DMA_Cmd(txDMAChannel, ENABLE);
}