본문 바로가기
임베디드 개발/STM32 (ARM Cortex-M)

STM32 , 74HC595 시프트 레지스터로 FND 제어하기 , 카운터 / 시계 ( SysTick 타이머 사용)

by eteo 2022. 6. 2.

 

 

 

시프트 레지스터 두개로 4개의 FND를 제어할 수 있는 아두이노 호환보드를 사용하여 카운터 및 시계를 만들어 보겠습니다.

 

 

 

0부터 9999 까지 세는 카운터

 

 

 

시계, 왼쪽 두자리가 분이고 오른쪽 두자리가 초

 

 

확장보드의 회로도

 

 

 

 

직렬입력-병렬출력 시프트레지스터인 74595칩이 두 개가 달렸다. 왼쪽 칩의 SDI가 아두이노 8번핀과 연결되어 있고 Shift Clock 과 Latch Clock은 각각 7번핀 4번핀에서 두 칩에 동시에 들어간다.


FND에 표시할 숫자를 결정하는 부분은 오른쪽 74595 칩이고 그 숫자를 4개중에 어디에 표시할 지는 왼쪽 74595 칩에 의해 결정된다. 예를 들어 왼쪽 74595칩의 QA, QB, QC, QD가 모두 HIGH라면 같은 숫자가 들어간다.

 

그리고 delay 없이 1번, 2번, 3번, 4번 FND를 다른 숫자로 번갈아 고속으로 키면 동시에 움직이는 것처럼 보인다. 인간의 눈으로 불연속을 인지하지 못하게 하려면 60Hz(약 16ms)로 새로고치기만 하면 된다.

 

Serial Data Input 으로 데이터가 들어간 후에 Shift Clock 상승엣지에 데이터가 한칸 씩 이동하고 Latch Clock 상승엣지에 현재 저장된 데이터가 모두 병렬로 출력된다. 위 경우에는 칩 두개가 연결된 형태로 왼쪽칩의 QH와 같은 값이 SDO에서 나와 다음칩의 SDI로 들어가고 Shift Clock 상승엣지에 다음칩 QA위치로 이동한다.

 

 

 

 

74595 칩의 로직 다이어그램

 

 

 

 

참고로 STM32에서 아두이노의 8,7,4번 핀과 호환되는 것은 PF12,13,14 핀이다. 해당핀을 GPIO 아웃풋으로 잡아 PF12포트로는 데이터를 보내고 PF13, PF14는 shift clock / latch clock 을 내보낸다.

 

 

 

 

 

 

 

/* USER CODE BEGIN PFP */
void DisplayFND(uint8_t fnd, uint8_t location);
uint8_t ttl7447(uint8_t num);
void counter0to9999(uint8_t num[4], uint32_t tick);
void count_time(uint8_t num[4], uint32_t tick);
/* USER CODE END PFP */

 

 

 

 

 

  /* USER CODE BEGIN 2 */

  uint32_t start_tick = HAL_GetTick();
  uint32_t current_tick;
  uint32_t tick=0;
  uint8_t count[4] = {0,};

  /* USER CODE END 2 */

 

루프 직전에 HAL_GetTick() 함수의 반환값을 start_tick 변수에 저장한다.

 

 

 

 

 

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

	  current_tick = HAL_GetTick();
	  if(current_tick - start_tick >= 1000){
		  tick++;	// count seconds
		  start_tick = current_tick;
	  }

//	  counter0to9999(count, tick);
	  count_time(count, tick);

	  DisplayFND(ttl7447(count[0]), 1);	// thousands
	  DisplayFND(ttl7447(count[1]), 2);	// hundreds
	  DisplayFND(ttl7447(count[2]), 3);	// tens
	  DisplayFND(ttl7447(count[3]), 4);	// ones


    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

 

while문 내 HAL_getTick(); 함수의 반환을 current_tick에 저장하고, current_tick과 start_tick의 차가 1000ms 보다 커지면 tick이라는 변수를 ++해서 프로그램 실행 후 지난 초를 구하고, start_tick을 current_tick으로 바꿔서 1초간격마다 if문 안에 들어올 수 있게 한다.

 

HAL_getTick(); 함수를 따라가보면 그냥 SysTick 타이머에 의해 실행후 밀리초 단위로 올라가는 uwTick 을 리턴해주는 건데 그냥 (uwTick % 1000 == 0) 이런식으로 활용할 수 있지 않을까 싶었는데 너무 빨라서 그런지 의도한대로 움직이지 않았다. 그래서 따로 지역변수를 두고 == 1000 이라고 하기보다 >= 1000 으로 해주어야 하는 것 같다.

 

 

 

 

 

 

 

 

10진수를 받아서 7 segment 에 해당하는 신호로 바꿔주는 함수

입력이 BCD가 아니라 7447이라고 할 순 없지만 그냥 그렇게 지었다. 나중에는 Decto7seg로 refactor함.
호환보드에 달려있는 FND는 애노드 타입이라 0이면 불이 켜지고 1이면 불이 꺼진다.

 

애노드타입이라 0xff일때 모든 불이 꺼지니까 num이 0에서 9사이의 값이 아니라면 default로 빠져서 0xff를 리턴한다.

uint8_t ttl7447(uint8_t num){
	// for anode type
	switch(num){
	case 0:
		return 0xc0;
	case 1:
		return 0xf9;
	case 2:
		return 0xa4;
	case 3:
		return 0xb0;
	case 4:
		return 0x99;
	case 5:
		return 0x92;
	case 6:
		return 0x82;
	case 7:
		return 0xd8;
	case 8:
		return 0x80;
	case 9:
		return 0x90;
	default:
		return 0xff;
	}
}

 

 

 

 

 

 

 

 

74595 칩에 신호를 입력하는 함수

void DisplayFND(uint8_t fnd, uint8_t location){

	HAL_GPIO_WritePin(GPIOF, GPIO_PIN_14, 0);	// latch clock pin off

	uint16_t data = 0;
	data = fnd << 8;
	data |= 1 << (location-1);


	// for example, to display 1 to first FND
	// MSB 1111 1001 dddd 0001 LSB	(d means don't care)

	for(int i=0 ; i<16; i++){

		if(data >> (15-i) & 1){	// MSB first in
			HAL_GPIO_WritePin(GPIOF, GPIO_PIN_12, 1);	// Data line
		}else {
			HAL_GPIO_WritePin(GPIOF, GPIO_PIN_12, 0);	// Data line
		}

		HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, 1);	// shift clock pin on
		HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, 0);	// shift clock pin off
	}


	HAL_GPIO_WritePin(GPIOF, GPIO_PIN_14, 1);	// latch clock pin on
}

 

처음에 래치 클락핀 off

그리고 병력 출력시킬 값을 담을 16비트 변수 data 선언

먼저 FND 숫자 데이터를 8비트 왼쪽이동 한 뒤 data에 담고, FND가 총 4개인데 그중에 킬 위치를 location 매개변수로 받아서 해당 비트를 켠다.

16번 동안 데이터를 보내고 클락펄스를 주는 것을 반복한다. for문을 나오고 16개의 데이터가 다 들어찼을 때 Latch Clock 핀을 on 하여 병렬출력 시킨다.

예를들어 1, 2, 3, 4번 FND에 각각 숫자 1, 2, 3, 4 를 표시한다고 할때 아래 16개의 binary code가 h부터 c1까지 순서대로 들어가야 한다.

 

 

 

 

 

 

 

 

 

 

1초마다 증가하는 tick을 받아서 4자리 배열에 0~9999까지 또는 초/분 단위로 넣어주는 함수

main에서는 배열명으로 인수 전달한다.

void counter0to9999(uint8_t num[4], uint32_t tick){
	num[0] = (tick%10000)/1000;
	num[1] = (tick%1000)/100;
	num[2] = (tick%100)/10;
	num[3] = tick%10;
}

void count_time(uint8_t num[4], uint32_t tick){
	num[3] = (tick%60)%10;
	num[2] = (tick%60)/10;
	num[1] = ((tick%3600)/60)%10;
	num[0] = ((tick%3600)/60)/10;

}