이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.


STM32F103C8T6을 이용해서 16자 2행 문자형 LCD를 동작시켜 보았습니다.




1. 회로 구성



다음은 4비트로 동작시키기 위한 회로도 입니다.




 


실제로는 LCD와 STM32F103 보드 사이에 레벨시프트 회로를 사용했습니다. LCD는 5V 전원을 사용하고 STM32F103은 3.3V 전원을 사용하기 때문에 전압 변환을 해주는 레벨시프트 회로를 사용했습니다. 매뉴얼의 1페이지 맨 아래 줄에 -26/37/51/80 I/Os, all mappable on 16 external interrupt vectors and almost all 5 V-tolerant로 되어 있습니다. (STM32F103 매뉴얼 링크) 그러나 모든 GPIO가 5V-tolerant가 아니므로 매뉴얼을 잘 살펴아야 합니다. 매뉴얼의 pinout 부분을 보면 stm32f103c8t6은 LQFP48 패키지인데, PA0 ~ PA7은 5V-tolerant가 아닙니다. (5V-tolerant핀은 I/O Level에 FT라고 표시되어 있습니다.) 따라서 레벨시프트 회로 사용을 권장합니다. 레벨시프트 회로가 없는 경우에는 대부분 1㏀ 정도의 저항을 넣어주어도 됩니다.





2. 프로젝트 만들기

STM32CubeMX로 프로젝트를 만듭니다.



①  Pinout 기능을 지정



RCC 항목의 HSE를 Crystal/Ceramic resonator로 지정하고, SYS 항목의 Debug를 Serial Wire로 지정합니다.


다른 항목들은 위의 회로도에서 연결한 것과 같이 지정합니다. PA8~PA11, PB12, PB13, PC13을 GPIO_Output으로 지정합니다. GPIO_Output으로 지정하고 난 다음에 오른쪽 마우스 버튼으로 클릭하면, Enter User Label이 나오는데 여기에 DB4~DB7, RS, RW, LED 등의 레이블을 입력합니다.


STM32F103의 PC13은 이 보드 내의 led와 연결되어 있습니다.

Timer2를 이용해서 LED를 깜빡이도록 할 예정입니다. 이를 위해서 [Pinout] 탭에서 TIM2 항목의 Clock Source를 Internal Clock으로 지정합니다.







② Clock Configguration


[Clock Configulation] 탭을 눌러 다음 그림과 같이 클럭을 설정합니다. Lcd가 빠르게 동작하는 기기도 아니고, 테스트용으로 작성하는 프로그램이므로 HCLK를 8Mhz로 지정했습니다.



 


③ Configuration


[Configuration] 탭을 클릭하여 나온 화면에서 [GPIO] 버튼을 눌러 다음 그림과 같이 설정합니다. 특별히 수정한 값은 없습니다.



 



[TIM2] 버튼을 눌러 다음 그림과 Prescaler에 7999, Counter Period에 499을 입력합니다. Prescaler 값과 Counter Period 값은 0부터 시작합니다. Prescaler 값에 7999를 입력하면 0부터 7999까지 진행한 다음에 다시 0으로 되돌아가므로, 실제로는 8000으로 나눈 값과 같습니다. Counter Period도 마찬가지로 499를 입력하면 500회 실행합니다.





Internal Clock이 8Mhz로 동작하므로, Prescaler에서 8000 분주를 하도록 하면 Period는 약 1mS가 됩니다. 500 Period마다 TIM2 인터럽트가 발생하도록 해서, 이 인터럽트 서비스 루틴에서 LED를 토글하면 약 1초마다 한 번씩 LED가 깜빡이게 될 것입니다.


실제로 인터럽트가 발생하도록 하려면 NVIC를 설정해 주어야 합니다. 위의 TIM2 Configuration 화면에서 [NVIC Settings] 탭을 누른 후에 다음 그림과 같이 TIM2 global interrupt의 Enable을 체크해 줍니다.



프로젝트를 저장하고, [Project] 메뉴에서 [Generate Code]와 [Generate Report]를 각각 실행합니다.





3. 코딩하기

 

 

1) 지연 함수 추가하기


문자형 lcd는 동작 속도가 느려서 시간을 지연하는 함수를 사용해야 합니다. HAL 라이브러리에서 제공하는 함수는 millisecond(mS) 단위라서 상대적으로 너무 깁니다. 인터넷에서 microsecond(uS) 단위로 시간을 지연시키는 라이브러리를 찾아 봤습니다. 원 출처는 https://www.controllerstech.com/create-1-microsecond-delay-stm32/인 것 같은데, 사이트가 원활하게 열리지 않습니다. 인터넷에서 dwt_stm32_delay.h와 dwt_stm32_delay.c를 찾아서 dwt_stm32_delay.h는 [Inc] 폴더에, dwt_stm32_delay.c는 [Src] 폴더에 추가합니다.

 

dwt_stm32_delay 라이브러리를 이용하기 위해서 main.c에 다음과 같이 코드를 추가합니다. 주석 처리된 부분은 STM32CubeMX가 main.c에 자동으로 추가한 것으로 입력해야할 코드의 위치를 명시하기 위해 같이 제시하였습니다. 코드 중 (중략)은 두 코드가 연속적으로 입력하는 것이 아니라는 의미로 삽입한 것입니다. 실제로 입력하는 코드는 아닙니다.

 

/* USER CODE BEGIN Includes */ 
#include "dwt_stm32_delay.h" 
/* USER CODE END Includes */
 (중략) 
  /* USER CODE BEGIN 2 */
  DWT_Delay_Init();
  /* USER CODE END 2 */

dwt_stm32_delay 라이브러리를 사용하기 위해서 dwt_stm32_delay.h 파일을 포함시켰고, DWT_Delay_Init() 함수를 호출하여 초기화시키고 있습니다.

 

main.c에서 다음과 같이 HAL_TIM_Base_Start_IT() 함수를 호출하여 TIM2의 인터럽트가 동작하도록 합니다. 주석은 코드를 입력할 위치를 확인하기 위해서 같이 적어 둡니다.

 

  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */

 


2) 문자형 LCD 제어

 

Attolic에서 프로젝트를 열고, 오른쪽의 [Project Explorer]에서 [Src] 폴더를 우측 마우스 버튼을 클릭합니다. 새로 나타난 팝업 메뉴에서 연속하여 [New], [Source File]을 선택한 다음 파일명으로 Clcd.c를 입력하여 프로젝트에 Clcd.c 파일을 추가합니다.

 



 

 

Clcd.c 파일에 4비트를 문자형 lcd에 전송하는 함수 ClcdWriteNibble() 함수를 만듭니다.

void ClcdWriteNibble(uint8_t nibble)
{
  DWT_Delay_us(100);
  DB4_GPIO_Port->ODR = ((nibble & 0x0F) << 8);
  E_GPIO_Port->BSRR = E_Pin;
  E_GPIO_Port->BRR = E_Pin;
}

DB4_GPIO_Port는 STM32CubeMX에서 DB4 핀을 GPIO_Output으로 지정하였으므로, Generate Code 작업을 실행할 때에 main.h 안에 GPIOA로 매크로로 저장된 것입니다. ClcdWriteNibble() 함수는 nibble을 매개 변수로 받아 하위 4비트 값을 왼쪽으로 8번 시프트하여, DB4_GPIO_Port의 ODR 레지스터에 출력합니다. 즉, 매개 변수 nibble의 하위 4비트가 PA8~PA11로 출력됩니다.

 

이후 E_GPIO_PORT->BSRR = E_Pin에 의해서 LCD의 E핀이 high로 되었다가, E_GPIO_PORT->BRR = E_Pin에 의해서 low로 됨으로써 하나의 펄스를 생성합니다.



ClcdWriteNibble() 함수를 이용해서 한 바이트를 전송하는 ClcdWriteByte() 함수를 만듭니다.

void ClcdWriteByte(uint8_t byte)
{
  ClcdWriteNibble(byte >> 4);
  ClcdWriteNibble(byte);
}

ClcdWriteByte() 함수는 byte를 매개변수로 받아 ClcdWriteNibble() 함수를 이용하여 상위 4비트를 전송한 후에 하위 4비트를 전송함으로써 한 바이트를 문자형 LCD로 보냅니다.


ClcdWriteByte() 함수를 이용하여 문자형 LCD에 제어 명령을 보내는 ClcdWriteCommand() 함수와 데이터를 보내는 ClcdWriteData() 함수를 만듭니다.

void ClcdWriteCommand(uint8_t cmd)
{
  RS_GPIO_Port->BRR = RS_Pin;
  DWT_Delay_us(1000);
  ClcdWriteByte(cmd);
}

void ClcdWriteData(uint8_t data)
{
  RS_GPIO_Port->BSRR = RS_Pin;
  DWT_Delay_us(1000);
  ClcdWriteByte(data);
}


두 함수는 RS_Pin의 상태만 다릅니다. ClcdWriteCommand() 함수는 RS_Pin을 low로 설정하지만 ClcdWriteData() 함수는 high로 설정합니다.

ClcdWriteCommand() 함수를 이용하여 문자형 LCD를 초기화하는 함수 ClcdInit() 함수를 만듭니다.

void ClcdInit(void)
{
  HAL_Delay(16);
  ClcdWriteNibble(0x03);
  HAL_Delay(3);
  ClcdWriteNibble(0x03);
  HAL_Delay(100);
  ClcdWriteNibble(0x03);
  ClcdWriteNibble(0x02);
  ClcdWriteCommand(0x28);  //4bit mode
  ClcdWriteCommand(0x08);  //display off
  ClcdWriteCommand(0x01);  //clrscr
  ClcdWriteCommand(0x06);  //auto increment
  ClcdWriteCommand(0x0C);  //display on, cursor off
}

 

문자형 LCD 제어 명령에 관한 설명은 아래에 링크한 필자의 다른 글을 참조하시기 바랍니다. avr 어셈블리어로 문자형 lcd를 다루는 글입니다.

http://mcus.tistory.com/37

 


문자형 LCD를 제어할 때에 유용하게 쓰일만한 몇 개의 함수들을 추가로 작성합니다.

void ClcdGotoxy(int x, int y)
{
  if (y == 0) ClcdWriteCommand((1 << 7) | 0x00 | x);
  if (y == 1) ClcdWriteCommand((1 << 7) | 0x40 | x);
}

void ClcdClrscr(void)
{
  ClcdWriteCommand(0x01);
}

void ClcdPuts(uint8_t *p)
{
  while (*p != '\0') ClcdWriteData(*p++);
}

void ClcdPutsGotoxy(int x, int y, uint8_t *p)
{
  ClcdGotoxy(x, y);
  ClcdPuts(p);
}

 

함수들의 이름에서 짐작할 수 있듯이 ClcdGotoxy() 함수는 LCD에 글자를 표시할 위치를 지정하는 기능을 하고, ClcdClrscr() 함수는 화면을 지우는 기능을 합니다. ClcdPuts() 함수는 문자열을 출력하며, ClcdPutsGotoxy() 함수는 지정된 위치에 문자열을 출력합니다. 하나의 문자를 출력하는 경우에는 ClcdWriteData() 함수를 호출하면 되므로 굳이 따로 만들지 않았습니다.

ClcdGotoxy() 함수의 경우 x 좌표와 y좌표는 모두 0으로부터 시작합니다. 즉 1행의 1열에 표시하고자할 경우에는 ClcdGotoxy(0,0)으로 호출해야 합니다.

Project Explorer 화면에서 [Inc] 폴더를 우측 마우스 버튼으로 클릭한 후에 [New], [Header File]을 선택합니다. 파일명으로 Clcd.h를 입력한 후에 위에서 만든 함수들의 원형을 입력합니다.

/*
 * Clcd.h
 *
 *  Created on: 2018. 7. 26.
 *      Author: Jeong
 */

#ifndef CLCD_H_
#define CLCD_H_

void ClcdWriteNibble(uint8_t nibble);
void ClcdWriteByte(uint8_t byte);
void ClcdWriteCommand(uint8_t cmd);
void ClcdWriteData(uint8_t data);
void ClcdInit(void);
void ClcdGotoxy(int x, int y);
void ClcdClrscr(void);
void ClcdPuts(uint8_t *p);
void ClcdPutsGotoxy(int x, int y, uint8_t *p);

#endif /* CLCD_H_ */


 

 


3) Timer 2 인터럽트를 이용하여 LED 토글하기

 

STM32CubeMX로 프로젝트를 생성할 때에 Timer 2(TIM2)에서 500mS마다 인터럽트가 발생하도록 했습니다. 이 인터럽트 서비스 처리 루틴에서 PC13에 달려 있는 led를 토글시키도록 합니다.

 

TIM2 인터럽트가 발생하면 TIM2_IRQHandler() 함수가 호출됩니다. 이 함수는 Project Explorer 창의 [Src] 폴더 안에 있는 stm32f1xx_it.c에 정의되어 있습니다. 이 파일 안에서 void TIM2_IRQHandler() 함수를 찾습니다. 이 함수 안에 led가 연결되어 있는 PC13 핀을 토글하도록 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); 명령을 입력합니다. 다음은 완성된 void TIM2_IRQHandler() 함수입니다.


void TIM2_IRQHandler(void)
{
  /* USER CODE BEGIN TIM2_IRQn 0 */
  HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
  /* USER CODE END TIM2_IRQn 0 */
  HAL_TIM_IRQHandler(&htim2);
  /* USER CODE BEGIN TIM2_IRQn 1 */

  /* USER CODE END TIM2_IRQn 1 */
}





4) main.c 완성하기

Clcd 제어 함수들을 사용하기 위해서 main.c 파일을 수정합니다.


 

① Clcd.h 파일 포함시키기

 

앞에서 dwt_stm32_delay 라이브러리를 사용하기 위해 수정했던 곳에 다음과 같이 Clcd.h 파일을 포함시키는 명령을 넣습니다.

/* USER CODE BEGIN Includes*/ 
#include "dwt_stm32_delay.h"
#include "Clcd.h"
/* USER CODE END Includes */


 

② 문자형 lcd에 글자 출력하기

 

dwt_stm32_delay 라이브러리를 초기화 시킨 곳을 다음과 같이 수정하여 문자형 lcd에 글자들을 출력합니다.
 

HAL_Delay(100);
DWT_Delay_Init();               // dwt_stm32_delay 라이브러리 초기화
ClcdInit();                     // 문자형 lcd 초기화
ClcdPutsGotoxy(0, 0,(uint8_t *)"JBY...");
ClcdPutsGotoxy(2, 1,(uint8_t *)"Ready!");



zip으로 압축한 소스 파일을 첨부합니다.

STM32F103Clcd.zip




 

문자형 LCD 동작하는 사진입니다.

 




블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,