개요

ADC 의 원리를 이해하고 TFT LCD 를 제어한다.

기본 개념 (배경 지식)

TFT LCD

LCD 구조

LCD 는 구동 방식에 따라 Passive matrix 구조와 Active matrix 구조로 나뉜다.


1. Passive matrix LCD

각 화소점이 두 개의 겹쳐지는 전극 단자에 의해 구동되므로 독립적으로 작동하지 못한다. Contrast 와 응답속도, 색 표시 특성의 확보가 어렵다. TN, STN LCD 가 이에 해당된다.


2. Active matrix LCD (AMLCD)

각 화소 하나 하나에 트랜지스터가 형성되어 있어 화소가 켜짐과 꺼짐 동작을 각각 독립적으로 제어할 수 있다. 상대적으로 고화질, 빠른 응답속도를 얻을 수 있으며 TFT LCD 가 이에 해당된다.


TFT LCD 원리

TFT(Thin Film Transistor : 박막 트랜지스터) 는 액정 표시 방식 중 하나로 액정 화소 하나 하나에 반도체 스위치를 붙여 표시를 제어하는 방식이다.

LCD(Liquid Ctystal Display) 는 2개의 얇은 유리판 사이에 액체와 고체의 중간적 특성을 가지는 액정(Liquid Crystal)을 주입하여 전원 공급 시 액정 분자의 배열을 변화시켜 명암의 변화를 이용하여 영상을 표시하는 디스플레이다.

TFT LCD 는 크게 비정질 실리콘 등의 반도체 박막이 형성되어 있는 아래 유리기판과 Color Filter 가 형성되어 있는 윗 유리기판 그리고 그 사이에 주입된 액정(Liquid Crystall) 로 구성되어 있다.

액정은 인가된 전압에 따라 분자구조를 달리하여 빛의 투과를 제어하는데, 이때 두 전극 사이의 전압은 아래 유리기판에 존재하는 TFT를 통해 전압을 조잘한다.

아래 유리기판 밑의 백라이트 유닛에서 빛을 공급하면 빛은 액정의 배열을 따라 지나가 윗 유리기판의 Color Filter 를 통해 나가면서 색을 표한하게 된다.


TFT LCD Timing characteristics

TFT LCD 는 사용하는 interface 에 따라 Timing 특성이 바뀐다.

  • Interface 종류

  • Serial Peripheral Interface : master/slave 방식의 통신. Clock 을 동기화 한 후, 8 bits 의 데이터를 통신

  • Parallel 6800 series Interface : read/write(R/W) 와 enable(E) 제어신호를 사용하여 read, write 를 제어

  • Parallel 8080 series Interface : read(RD), write(WR) 제어신호를 사용하여 read, write 를 제어

이 중 이번 실험에서 Parallel 8080 series Interface 를 사용한다.


Parallel 8080 series Interface 의 제어신호

  • CS (Chip Select) : low 일 때, chip이 동작되도록 하는 신호

  • RS = D/C (Data/Command) : low 시, command 전송. high 시, data 전송

  • WR (Write) : low 에서 high 로 변할 때, data 를 display RAM 에 write 한다.

  • RD (Read) : low 에서 high 로 변할 때, display RAM 에서 data 를 read 한다.



  • CS 를 low 로 하여 chip 을 동작시킨다.

  • RS(=D/C) 가 low 이면 command, high 이면 display data 가 register 에 write 된다.

  • write cycle 이므로 RD 는 항상 high 로 둔다.

  • WR 이 low 에서 high 로 display RAM 에 data 또는 register 에 command 를 write 한다.


  • CS 를 low 로 하여 chip 을 동작시킨다.

  • RS 를 high 로 하여 display RAM 에 저장된 display data 를 읽어오도록 한다.

  • read cycle 이므로 WR 은 high 를 유지한다.

  • RD 가 low 에서 high 로 바뀔 때 display data 를 읽는다.



ADC & DAC

컴퓨터는 2진 digital data 를 사용하므로 컴퓨터가 신호를 감지할 수 있도록 analog 신호를 digital 신호로 변환한다.

digital 신호는 analog 신호보다 명확하고 규칙적이므로 손실 없이 data 를 저장 또는 이용할 수 있다.


ADC (Analog to Digital Conversion)

온도, 습도, 조도 등의 analog 물리량을 digital 신호로 변환하는 것.

A/D Converter 의 과정

1) Analog Siganl

2) Sampling Pulse (sampling 속도 결정)

3) Sampled Signal

4) Sampled / Hold Signal 및 Digit 화


ADC 사용 목적

물리적 양을 측정하는 센서의 값들은 Analog 값이다. 예를 들면 소리의 크기, 빛의 양, 압력 등이 이에 속한다. 이 값들을 쓰기 위해선 디지털로 샘플링을 해야하는데, 이를 ADC 라고 한다.


ADC 사용 예시

  • 조도 센서

센서에서 전달받은 analog 신호를 ADC 가 digital 신호로 바꿔준다.


- Single ADC Block diagram

실험 보드에는 2개의 12bits ADC 가 존재한다. 각 ADC 는 16 개의 체널을 가진다.

외부에서 들어온 신호를 ADC 를 통해 ADC interrupt 를 발생시킨다.


DAC (Digital to Analog Conversion)

ADC 의 역방향 처리 과정으로 0과 1로 표현되는 digital 신호를 사람이 들을 수 있는 analog 신호로 변환시켜주는 장치이다.

정해진 bit 수로 인해 정밀도는 제한적이다.

D/A Converter 의 과정

1) 증폭기(Amplifier)를 통해 n 개의 digital 신호를 analog 신호의 크기로 바꾸어 스텝 신호를 출력

2) 필터 회로를 사용하여 출력된 스텝 신호를 원래 신호에 가깝게 복원



실험 방법

그림과 같이 TFT-LCD 를 보드에 결합한다.


그리고 조도센서를 보드에 납땜한다. 출력가 입력포트를 주의해서 납땜해야 한다.


lcd.h, lcd.c, touch.h, touch.c, font.h, font.c 를 라이브러리에 추가한다.


LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o(RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00008000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

플래시 업로드를 위한 sct 파일을 위와 같이 바꿔준다. 지금까지는 0x00008000 으로 실험했다. 그 이유는 이전보다 더 많은 라이브러리를 사용하기 때문에, 더 넓은 메모리 공간이 필요해서 확장한 것이다.

만약 sct 파일을 이전과 같이 0x00008000 처럼 사용한다면, no space 에러가 발생한다.


그림과 같이 타이밍 그래프를 참고하여 lcd.c 의 LCD_WR_REG, LCD_WR_DATA, LCD_ReadReg, LCD_WriteReg 함수를 작성한다.


void LCD_WR_REG(uint16_t LCD_Reg) {
	GPIO_SetBits(GPIOD, GPIO_Pin_15); //LCD_RD(1)
	GPIO_ResetBits(GPIOD, GPIO_Pin_13); //LCD_RS(0);
	GPIO_ResetBits(GPIOC, GPIO_Pin_6); //LCD_CS(0);
	GPIO_ResetBits(GPIOD, GPIO_Pin_14); //LCD_WR(0);
	DataToWrite(LCD_Reg);
	GPIO_SetBits(GPIOC, GPIO_Pin_6); //LCD_CS(1);
	GPIO_SetBits(GPIOD, GPIO_Pin_14); //LCD_WR(1);
}

void LCD_WR_DATA(uint16_t LCD_Data) {
	GPIO_SetBits(GPIOD, GPIO_Pin_15); //LCD_RD(1)
	GPIO_SetBits(GPIOD, GPIO_Pin_13); //LCD_RS(1);
	GPIO_ResetBits(GPIOC, GPIO_Pin_6); //LCD_CS(0);
	GPIO_ResetBits(GPIOD, GPIO_Pin_14); //LCD_WR(0);
	DataToWrite(LCD_Data);
	GPIO_SetBits(GPIOC, GPIO_Pin_6); //LCD_CS(1);
	GPIO_SetBits(GPIOD, GPIO_Pin_14); //LCD_WR(1);
} 


/* LCD ReadReg */
uint16_t LCD_ReadReg(uint16_t LCD_Reg)
{
	uint16_t temp;
	GPIOF->CRL=0x88888888;
	GPIOF->CRH=0x88888888;

	//LCD_WriteReg(LCD_Reg);
	GPIO_SetBits(GPIOD, GPIO_Pin_14);//LCD_WR(1);
	GPIO_SetBits(GPIOD, GPIO_Pin_13); //LCD_RS(1);
	GPIO_ResetBits(GPIOC, GPIO_Pin_6); //LCD_CS(0);
	GPIO_ResetBits(GPIOD, GPIO_Pin_15);//LCD_RD(0);
	temp = DataToRead();
	GPIO_SetBits(GPIOC, GPIO_Pin_6);//LCD_CS(1);
	GPIO_SetBits(GPIOD, GPIO_Pin_15);//LCD_RD(1);

	//////////////////////////////
	GPIOF->CRL=0x33333333;
	GPIOF->CRH=0x33333333;
	return temp;				    	 	
}


void LCD_WriteReg(uint16_t LCD_Reg ,uint16_t LCD_RegValue)
{
	LCD_WR_REG(LCD_Reg);
	LCD_WR_DATA(LCD_RegValue);
}



프로그램을 돌리면 다음과 같이 지정 위치의 좌표와 조도 값, 아래의 버튼을 이용하여 LED 를 조작할 수 있다.



결론

TFT-LCD 를 동작시키고, 화면에 글자와 도형을 표시하고 터치를 입력받아서 처리하는 실험이었다. 이 실험의 경우 Term-Project 에서 매우 중요하게 쓰일것 같다. 특히 스캐터 파일의 메모리 공간을 크게 잡는게 인상적이었다.

조도센서의 값을 lcd 에 표시해봤는데 생각보다 민감한 빛의 변화를 감지하지 못했다. 이는 하드웨어적 문제인것 같다.

또한 lcd.c 내부를 살펴보면서 Touch_GetXY 내부에서 T_INT 의 역할을 알게됐고, 다른 기본적인 draw 함수들도 매우 간단한 wrapper function 에 불과하다는것을 알게 됐다. 우리가 곧 구현할 키패드 구현에도 크게 작용할 것 같다.

하지만 이 실험에서 해결못한 이전에 찍었던 점을 지워주는 방법에 대해서는 좀 고민해봐야할것 같다. draw fill 을 통해서 이전 영역을 지워주면 될것 같았는데 잘 안됐다. 아마 내부적으로 좌표의 범위의 문제가 있어서 잘못된 동작으로 빠진것으로 추정된다.

전체 소스

#include <misc.h>
#include <stm32f10x.h>
#include <stm32f10x_exti.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_usart.h>
#include <stm32f10x_adc.h>
#include <lcd.h>
#include <Touch.h>
#include <stdio.h>
#include <stdlib.h>
uint16_t pos_x,pos_y;
uint16_t pix_x,pix_y;
uint16_t jodo;

// 버튼의 위치를 저장하는 변수
int button_pos[4][2];

// led 켜지고 꺼진 상태 토글
int button[] = {0, 0, 0, 0}; 

void delay(int i){
	int j;
	for(j=0; j<=i * 100000; j++);
}
void SysInit(void)
{
	/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
	/* Set HSION bit */
	RCC->CR |= (uint32_t)0x00000001;

	/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
	RCC->CFGR &= (uint32_t)0xF0FF0000;

	/* Reset HSEON, CSSON and PLLON bits */
	RCC->CR &= (uint32_t)0xFEF6FFFF;

	/* Reset HSEBYP bit */
	RCC->CR &= (uint32_t)0xFFFBFFFF;

	/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
	RCC->CFGR &= (uint32_t)0xFF80FFFF;

	/* Reset PLL2ON and PLL3ON bits */
	RCC->CR &= (uint32_t)0xEBFFFFFF;

	/* Disable all interrupts and clear pending bits  */
	RCC->CIR = 0x00FF0000;

	/* Reset CFGR2 register */
	RCC->CFGR2 = 0x00000000;
}

void SetSysClock(void)
{
	volatile uint32_t StartUpCounter = 0, HSEStatus = 0;

	/* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
	/* Enable HSE */
	RCC->CR |= ((uint32_t)RCC_CR_HSEON);

	/* Wait till HSE is ready and if Time out is reached exit */
	do
	{
		HSEStatus = RCC->CR & RCC_CR_HSERDY;
		StartUpCounter++;
	} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

	if ((RCC->CR & RCC_CR_HSERDY) != RESET)
	{
		HSEStatus = (uint32_t)0x01;
	}
	else
	{
		HSEStatus = (uint32_t)0x00;
	}

	if (HSEStatus == (uint32_t)0x01)
	{
		/* Enable Prefetch Buffer */
		FLASH->ACR |= FLASH_ACR_PRFTBE;

		/* Flash 0 wait state */
		FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
		FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_0;

		/* HCLK = SYSCLK = 48MHz */
		RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;

		/* PCLK2 = HCLK = 48MHz */
		RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;

		/* PCLK1 = HCLK  = 24MHz  */
		RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV1;

		/* Configure PLLs ------------------------------------------------------*/
		/* PLL configuration: PLLCLK = PREDIV1 * 6 = 48MHz */
		RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
		RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
				RCC_CFGR_PLLMULL6);

		/* PLL2 configuration: PLL2CLK = HSE/5 * 8 = 40MHz  */
		/* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8MHz */
		RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
				RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
		RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
				RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);

		/* Enable PLL2 */
		RCC->CR |= RCC_CR_PLL2ON;
		/* Wait till PLL2 is ready */
		while((RCC->CR & RCC_CR_PLL2RDY) == 0)
		{
		}

		/* Enable PLL */
		RCC->CR |= RCC_CR_PLLON;

		/* Wait till PLL is ready */
		while((RCC->CR & RCC_CR_PLLRDY) == 0)
		{
		}

		/* Select PLL as system clock source */
		RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
		RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;

		/* Wait till PLL is used as system clock source */
		while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
		{
		}
	}
	else
	{ /* If HSE fails to start-up, the application will have wrong clock
         configuration. User can add here some code to deal with this error */
	}
}

void set_ENABLE(void) {
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	 // interrupt
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);     // RCC GPIO E
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);     // RCC GPIO C
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);     // RCC GPIO D
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	 // ADC1
}

void set_PC1(void) {
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
}

void set_ADC(void) {
	ADC_InitTypeDef ADC_InitStructure;
	ADC_DeInit(ADC1);
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel = 1;
	ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_239Cycles5);
	ADC_Init(ADC1, &ADC_InitStructure);
}

void set_NVIC(void) {
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
	ADC_Cmd(ADC1, ENABLE);
}

void ADC_start(void) {
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1));
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1));
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}


void ADC1_2_IRQHandler(void) {
	uint16_t input;
	u8 str[10];
	double result;
//
////	while(ADC_GetFlagStatus(ADC1, 0x2)==RESET);
	input = ADC_GetConversionValue(ADC1);
	result = (double)input;
	jodo = result;
	ADC_ClearITPendingBit(ADC1,ADC_IT_EOC);
	//	LCD_DrawRectangle(pix_x, pix_y, pix_x+10, pix_y+10);
}

int main() {
	int rSize = 15;
	int i;
	unsigned int i2BS[] = { GPIO_BSRR_BS2, GPIO_BSRR_BS3, GPIO_BSRR_BS4,
			GPIO_BSRR_BS7 };
	unsigned int off[] = { GPIO_BRR_BR2, GPIO_BRR_BR3, GPIO_BRR_BR4,
			GPIO_BRR_BR7 };
	char str[10];
	int prevCircleX = -1, prevCircleY = -1;

	SystemInit();
	set_ENABLE();
	set_PC1();
	set_ADC();
	set_NVIC();
	LCD_Init();
	Touch_Configuration();
	Touch_Adjust();
	LCD_Clear(WHITE);
	ADC_start();
	GPIOD->CRL = (GPIO_CRL_MODE2_0 | GPIO_CRL_MODE3_0 | GPIO_CRL_MODE4_0 | GPIO_CRL_MODE7_0);

	while (1) {
		LCD_ShowString(1, 1, "Wed_team07", BLACK, WHITE);
		LCD_ShowNum(200, 1, jodo, 4, BLACK, WHITE);

		Touch_GetXY(&pos_x, &pos_y, 0);
		///

		for (i = 0; i <= 3; i++) {
			button_pos[i][0] = 60 * i + 30;
			button_pos[i][1] = 280;
			LCD_DrawRectangle(60 * i + 30 - rSize, 280 - rSize,
					60 * i + 30 + rSize, 280 + rSize);
		}

		Convert_Pos(pos_x, pos_y, &pix_x, &pix_y);

		for (i = 0; i <= 3; i++) {
			if (button_pos[i][0] - rSize <= pix_x
					&& pix_x <= button_pos[i][0] + rSize
					&& button_pos[i][1] - rSize <= pix_y
					&& pix_y <= button_pos[i][1] + rSize) {
				button[i] = (button[i] + 1) % 2;
				if (button[i])
					GPIOD->BSRR = i2BS[i]; // led 켜기
				else {
					GPIOD->BRR = i2BS[i]; // led  끄기
				}
			}
		}

		if(T_INT == 0)
		{
			if (prevCircleX != -1 && prevCircleY != -1) {
				LCD_Clear(WHITE);
			}
			Draw_Circle(pix_x, pix_y, 10);
			prevCircleX = pix_x;
			prevCircleY = pix_y;

			sprintf(str, "%d, %d", pix_x, pix_y);
			LCD_ShowString(100, 2, str, BLACK, WHITE);
			delay(1);

		}

	}

}