본문 바로가기
Robot/Device Control

[STM32F10x-StdPeriph] 3. SmartRobot Board 실습 - UART Input/Outpu

by lifeseed 2013. 11. 18.
0. 들어가기 전에

   이번 강좌에서는 UART 컨트롤을 해볼까 합니다. Boot-Up Sequence가 정상적으로 이루어 지면, Debugging 혹은 Host와의 기본적인 통신을 위해 일반적으로 UART를 살리게 됩니다.

이번 강좌의 목적은 UART를 통한 Character 입/출력을 Host PC의 Terminal을 통해 확인하는 과정입니다. 

역시 Base Code는 http://lifeseed.tistory.com/75 게시물의 첨부파일이며, UART통신을 위한 Library가 포함되어야 하므로 컴파일전 추가 수정사항이 필요합니다.

 

   SmartRobot Board의 J7-11, 12번 및 J8-20에 USBtoSerial 보드의 Rx, Tx 그리고 Ground에 각각 연결합니다. 물론 SmartRobot Board의 UART1을 위한 전용 핀에 연결하여도 됩니다. 그리고 STM32F10x의 UART Pin을 이용하게 되므로, UART1 과 핀을 공유하는 GPIO PA9,10은 앞의 강의에서 실습한 GPIO 입출력으로 사용할 수 없습니다.

 

srbd_bsp_ex.zip                 ::  Base Code 

srbd_bsp_printf_ex.zip     :: UART 수정 코드

 

1. 코드 작성 및 컴파일
 

 void setup(void)
{
 GPIO_InitTypeDef GPIO_InitStructure;

 USART_InitTypeDef USART_InitStructure;


 // RCC Configuration

 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE );


 // Port 설정

 /* J7-11 PA9 */
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);

 

 /* J7-12 PA10 */

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 GPIO_Init(GPIOA, &GPIO_InitStructure);

 

 // UART Port 설정

 USART_InitStructure.USART_BaudRate    = 115200  ;
 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);
 USART_Cmd(USART1, ENABLE);
}

 

void loop(void)
{

 char data = 0;

 if ( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
 {
  data = (u8)USART_ReceiveData(USART1);

 

  while ((USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);

  USART_SendData(USART1, (uint16_t)data);
 }

 

 

}


arch/main.c 의 setup 및 loop함수에 위와 같이 코딩하고 cs-make를 통하여 Build합니다.

 

컴파일 에러가 날 경우 gpio, usart library를 include해야 합니다.

1) arch/include/stm32f10x_conf.h 의 38, 47라인의 주석을 풀어줍니다.

 

/*  #include "stm32f10x_gpio.h" */ 

=>

#include "stm32f10x_gpio.h"

 

/*  #include "stm32f10x_usart.h" */ 

=>

#include "stm32f10x_usart.h" 

 

2) arch/STM32F10x_StdPeriph_Driver/src/Makefile.inc 의 18, 27번째 라인의 주석을 풀어 줍니다.

 

#STM32F10X_DRV_SRC  += stm32f10x_gpio.c

=>

STM32F10X_DRV_SRC  += stm32f10x_gpio.c

 

#STM32F10X_DRV_SRC  += stm32f10x_usart.c

=>

STM32F10X_DRV_SRC  += stm32f10x_usart.c 

 

그리고 다시 빌드를 하면  

stm32f103cb.bin 파일이 생성됩니다.
 arm-none-eabi-objcopy -O binary stm32f103cb.elf stm32f103cb.bin
arm-none-eabi-objdump -h -S -C stm32f103cb.elf > stm32f103cb.lss
arm-none-eabi-nm -n stm32f103cb.elf > stm32f103cb.sym
arm-none-eabi-size -A stm32f103cb.elf
stm32f103cb.elf  :
section            size        addr
.text              4780   134234112
.data                40   536870912
.heap              3072   536870952
.stack_dummy       1024   536870952
.ARM.attributes      41           0
.comment             48           0
.debug_info       11242           0
.debug_abbrev      2693           0
.debug_aranges      232           0
.debug_line        3764           0
.debug_str         4310           0
.debug_frame       1860           0
.debug_loc         7123           0
.debug_ranges       192           0
Total             40421
 
2. Download 및 실행
SW3을 누른채로 Reset하여 부트모드로 진입후 1번항목을 선택하고 TeraTerm등의 Terminal 창에서 Y-Modem으로 빌드된 stm32f103cb.bin을 전송합니다.
자세한 다운로드 방법은 http://lifeseed.tistory.com/73 의 3번 항목을 참조하시면 됩니다.
(저는 Teraterm V4.79를 사용하고 있습니다.)

 ================== Main Menu ============================

  Download Image To Flash ------- 1

  Upload Image From Flash ------- 2

  Execute ------------------------------ 3

==========================================================

Waiting for the file to be sent ... (press 'a' to abort)
C

 Programming Completed Successfully!
--------------------------------
 Name: stm32f103cb.bin
 Size: 4820       Bytes
-------------------

다운로드가 완료되면 Reset버튼을 눌러 코드를 실행합니다.
Terminal 창에 키보드를 입력하면 입력한 문자가 출력됨을 확인 할 수 있습니다.


3. Code 설명
1) USART1 Clock 인가
 

 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE );


RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE );
- STM32F10x의 USART1 port에 Clock을 인가해 실제 USART1이 동작하도록 합니다. 모든 Peri. 들은 사용전에 반드시 Clock을 인가해 주어야 합니다.

 

2) GPIOA Pin9번을 Alternate Function USART Tx모드로 설정 .

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);


3) GPIOB Pin10를 Alternate Function USART Rx모드로 설정 

 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);

 
4) USART1 Port 초기화 

 USART_InitStructure.USART_BaudRate    = 115200  ;
 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);
 USART_Cmd(USART1, ENABLE);

- USART Port의 초기화 부분입니다. Baudrate, wordlength, stop bit등을 변경하고 싶으면, 해당 멤버 값을 변경하시면 됩니다.

5) USART1 Port의 Rx를 통하여 들어온 데이터가 있는지 확인 

  if ( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
 {
 }

- 평소에는 RX Fifo가 비어 있으나, 데이터가 들어오면 RXNE라는 Status Bit가 세팅되어 데이터가 들어왔음을 알 수 있다.
 
6) USART1 Port의 Rx를 통하여 들어온 데이터 읽어오기 

  data = (u8)USART_ReceiveData(USART1);

 
7) 이전에 보낸 데이터가 보두 전송된 후 데이터 전송 

  while ((USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);

  USART_SendData(USART1, (uint16_t)data);

- USART1 Tx Port로 데이터를 보내기 전에 TX FIFO의 데이터가 비어 있는지를 확인합니다.
이 코드는 TX FIFO가 빌 때 까지 while문을 이용하여 기다리게 됩니다..
8) Arudino code와의 비교
역시 초기화 과정이 Simple한 API로 제공됩니다.
 
cf) Ardiono Code 

 void setup(void)
{
 Serial.begin(115200);

}

 

void loop(void)
{

 int length;

 char data;

 Serial.readBytes(&data, length);

 if(length>0)

 {

  Serial.write(data);

 } 

}

 
- 위 아두이노 예제는 직접 돌려 보지 못해 결과가 다소 상이할 수 있습니다. 기본적인 컨셉 정도로만 이해 해주시면 될 듯합니다.
 
 
4. Register Control
1) USART Base Address
USART는 1 ~ 3까지 총 3개의 Port를 제공하며 각각의 Register Base Address는 다음과 같다.

 USART1 Base Address : 0x40013800

 USART2 Base Address : 0x40004400

 USART3 Base Address : 0x40004800

 
cf) UART1는 APB2 Bust에 UART2,3는 각각 APB1 Bus에 연결되어 있다. 따라서 UART2, 3을 이용하기 위해서는 RCC_APB1PeriphClockCmd 함수를 이용하여 클럭을 Enable 시켜 주어야 한다.
 
2) Register Description

 

 

 
아래 그림은 일반적인 UART통신을 위한 환경 설정에 대한 내용으로 Teraterm에서 Capture하였다.

 

 
각각의 속성에 대한 설정들을 CR1, 2, 3 및 BRR Register를 통하여 이루어 진다.
특히 BRR Register에 기록된 값을 통하여 Baudrate가 결정되는데, 이에 대한 자세한 설명은 Naver Oroca Cafe에 GroundZero님의 강좌를 참조하자.
STM32F10xxx USART Baud Rate 설정법 (http://cafe.naver.com/openrt/2155)
 
5. UART를 이용한 API 
1) printf
내가 짠 코드가 얼마나 잘 돌고 있는지? 원하는 값은 제대로 들어가는지 코드가 동작중에 확인 할 수 있는 가장 일반적인 방법은 UART를 통한 printf 출력이라고 할 수 있다.
기본적으로 C 코딩을 접한 분들이라면 누구나 알고 있는 printf는 간단히 표현하면 각 변수타입의 값을 약속된 포맷으로 나열하면, 그 값을 출력해주는 함수라고 할 수 있다.
 
예를 들어 정수형, 문자형 값을 보고 싶으면 각각 "%d", "%c" 라는 포맷으로 출력이 가능하다.
printf 함수의 전문은 smart robot님이 작성한 코드를 lb_printf.c 파일로 추가하여 함께 컴파일되도록 하였으며, 함수명은 Lb_printf 로 정의되어 있다.
Lb_printf에서는 print_byte라는 함수를 통하여 위에서 설명한 UART output 동작을 통하여 terminal로 출력이 된다.
Lb_printf 코드의 세부적인 설명은 생략한다.
 
setup() 함수에서 UART 초기화 아래 부분에 코드 한줄을 추가해보자.

  Lb_printf("Start Application : %s\r\n", "smart robot");

 
컴파일 후 코드를 실행하면 아래와 같은 출력 결과를 얻을 수 있다.

 

 
 "smart robot"이라는 string을 %s 라는 포맷을 이용하여 출력한 결과이다.
 
아두이노에서는 Serial.print 이라는 함수를 통하여 이와 동일한 동작을 수행할 수 있다.
마찬가지로 setup 함수의 Serial.begin(115200) 함수 아래줄에 코드 한줄을 추가하면 위와 동일한 결과를 얻게된다.

 Serial.print("Start Application : %s\r\n", "smart robot"); 

 
2) GetKey
 UART Read 동작을 이용하여 사용자가 원하는 처리를 할 수 있다.
 3. 5), 6) 을 구성하여 입력받은 키값을 Return하는 함수를 구성해보자.

int GetKey(char *pkey)

  int ret = 0;

  if ( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
  {

     *key = (u8)USART_ReceiveData(USART1);

     ret = 1;
  }

  return ret;

}

- UART를 통해 들어온 데이터가 있으면 1을 그렇지 않으면 0을 반환하는 함수이다.
이함수를 통하여 아래와 같이 사용자에 의해 선택적인 동작을 할 수 있다.
 
loop 함수를 아래와 같이 구성해보자. 

   char c;

 

   if(GetKey(&c)==1)

  {

     switch(c)

     {

      case '0':

        Lb_printf("pressed 0\r\n");

        break;

      case '1':

        Lb_printf("pressed 1\r\n");

        break;

      default:

        Lb_printf("%c", c);

        break;

     }

  }

- 0과 1을 입력할 경우 각각 pressed 0, pressed 1을 출력하고 그 외의 경우는 위에서 작성한 예제와 동일하게 UART입력을 바로 출력하는 동작을 수행한다.
 

 

 
 
UART에 대한 설명은 여기서 정리하고 다음 강좌에서는 아두이노의 AnalogRead에 해당하는 ADC 처리에 대해 정리해 보고자 한다. 
 
[참고자료]

[1] CD00171190 STM32F1xx Reference Manual
[2]
[3]