시스템 온 칩(SoC) 과제 ... #2 - 🖥️Basys3를 활용한 다기능 카운터, 🧮계산기와 UART 통신을 통한 🤖 로봇 조정

TaeUk·2023년 11월 22일
0

Project 정리

목록 보기
3/6
post-thumbnail

0. 시작에 앞서...

이 Project는 대학교 3학년 때, 수강하였던 "시스템 온 칩(SoC)"라는 교과목으로 강의 수강 중 제출해야 되는 최종 과제였으며, 1차 프로젝트인 시스템 온 칩(SoC) 과제...#1을 기반으로 최종 프로젝트를 구성하였다.

1차 프로젝트에 비해 다양한 기능을 추가하였으며, 그에 따라 구현 코드도 기하급수적으로 늘어났다.

아래의 코드 중 각 Function에 대한 기능 설명만 작성하였고, 가독성을 위해 세부적인 기능은 업로드하지 않았다.


1. SoC 최종 Project 설계 조건

  • Project는 다양한 주제를 선택하여 Digilent 사의 Basys3 보드를 이용한 시스템을 구현해야 한다.

  • 시스템은 AHB-Lite Verilog 모듈을 결합하여 자신만의 컴퓨터 시스템을 작성한다. 임베디드 시스템에 어셈블리어 혹은 C 언어로 프로그램 또한 작성하여 컴퓨터 시스템이 수행되어야 한다.

  • 해당 입력은 슬라이딩 스위치, 버튼, 그리고 조도 센서 및 본인이 원하는 모듈을 결합하면 다양한 입력이 가능하다.

  • 출력 또한 LED 출력 및 7-segment 출력, VGA 출력, Analog 출력 및 본인이 원하는 모듈을 결합하면 다양한 출력 형태가 가능하다.

"Digilent" 사의 Basys3 FPGA Borad


2. Project 주제 선정

SoC 최종 Project의 주제로 "다기능 카운터, 계산기와 UART 통신을 통한 로봇 조정"을 선정하였으며, 보드에 내장되어있는 LED 15개, SW개, KEY 스위치 5개와 Pmod JA, JB를 사용하여 Project를 구성하였다.

Pmod JA는 UART 통신을 하여 외부의 다른 프로세서와 통신을 할 수 있도록 만들었고, Pmod JB는 GPIO로 사용하여 JB1-4는 Input, JB7-10은 Output으로 사용할 수 있도록 구성하였다.

실제로는 JB1에 LED를 연결하여 JB7 핀만 사용하도록 구성하였다.

  • Basys3에 내장된 장치를 통한 구현 기능 및 Pmod를 사용한 주변 장치 제어

최종 Project를 구성하기 전, "7 Segments에 결과값을 출력하는 7bit 감산, 가산기"를 먼저 설계하여 최종 Project 구성에 사용하였다.

감산, 가산이 가능한 7bit 연산기를 미리 만들어, 최종 Project를 상향식 설계를 통해 구성하였다.

또한 앞서 언급하였듯이 이번 프로젝트에서 UART 통신을 하여 외부의 프로세서와 통신 및 제어를 포함하였다.

로봇을 제어하기 위해 32bit, 3.3V 프로세서인 ESP32-WROOM-32D을 사용하였다.

  • Basys3에 Pmod를 통해 연결한 최종 프로젝트 회로도

ESP32-WROOM-32D에 2개의 서보모터를 부착하여 간단한 로봇을 제작하였으며 이를 Basys3에서 데이터를 받아 동작을 제어하도록 제작하였으며, 위 그림은 최종적으로 Basys3에 ESP32-WROOM-32D 및 다른 주변 장치를 연결한 회로이다.


3. 동작 목표 및 기능 정리

설계된 결과물은 크게 다기능 카운터, 계산기와 UART 통신을 통한 작은 로봇 제어의 두 가지 기능으로 나뉜다. 이에 따라 두 가지 기능으로 나눠서 설명하였다.

🕹️ UART 통신을 통한 작은 로봇 제어

ESP32-WROOM-32D에 연결된 2개의 서보모터로 구성된 로봇을 Basys3가 제어하기 위해 UART 통신을 사용하였고, 이는 Pmod의 JA2와 JA3 Pin을 통해 UART 통신을 구성하였다.

Basys3의 Interface Specification 참고 매뉴얼을 보면 알 수 있듯이 JA2를 TX, JA3를 RX로 연결하여 UART 통신을 할 수 있었다.

  • 래퍼런스 매뉴얼의 Pmod 자료

자료 출처 : Basys3 reference

Basys3에 내장되어 있는 SW4를 로봇 제어 신호로 사용하여 Switch의 활성화 여부에 따라 ESP32-WROOM-32D가 연결된 서보 모터를 제어하여 로봇을 동작하게 된다.

  • 180도 회전의 서보 모터를 사용하여 구성한 소형 로봇

위 그림을 참고하여 서보 모터 로봇을 구성하였으며, ESP32-WROOM-32D의 전원이 인가되면 앞다리와 뒷다리의 서보 모터가 회전 가능한 각도인 180도 중 절반인 90도로 정렬하게 된다.

이후 어느 정도의 안정될 시간을 거친 후, 앞다리 역할을 하는 서보 모터가 왼쪽으로 20도 회전한 후 다시 오른쪽으로 40도 회전하여 양방향으로 총 40도로 움직인다.

뒷다리도 앞다리와 동일하게 동작하며 순차적으로 서보 모터를 동작하여 앞으로 진행할 수 있게 된다.

⏱️ 다기능 카운터, 계산기

SW[0]~SW[4]의 입력은 시스템의 mode를 결정한다.

  • SW[0]
    업카운터로 동작을 하며, HW Timer를 통해 값이 하나씩 증가한다. up-key를 사용하면 Timer를 초기화할 수 있다.

System 동작 Setting key(Tact Switch)

  • SW[1]
    스탑워치로 동작을 하며, HW Timer를 통해 값이 하나씩 감소하고, left-key를 통해 값을 십의 자리 단위로 증가시킬 수 있고, right-key를 통해 값을 십의 자리 단위로 감소시킬 수 있다.
    down-key를 통해 스탑워치를 시작하고 일시정지할 수 있으며, up-key를 사용하면 스탑워치를 초기화할 수 있다. 또한 스탑워치가 카운트다운을 해서 0에 도달하면 JB7핀을 통해 LED가 점멸하게 된다.

  • SW[2]
    랜덤 숫자 발생기/곱셈기로 동작을 하며, 4자리의 난수를 받아와 디스플레이시켜주거나 SW[9:5] 와 SW[15:10]을 곱해서 디스플레이 해준다.
    랜덤 숫자 발생기, 곱셈기 모드를 변경하는 방법은 middle-key를 누를 때마다 변경된다.

  • SW[3]
    덧셈기/뺄셈기로 동작을 하며, SW[9:5]와 SW[15:10]을 더하거나 빼서 디스플레이하고, middle-key를 누를 때마다 덧셈과 뺄셈 모드가 변경된다.

  • SW[4]
    앞서 설명한 로봇 제어를 위해 사용되며, 스위치가 ON이 되면 로봇이 전진한다.


4. Project 설계 과정

📀 H/W 구성

🔨 Basys_Master.xdc

프로젝트에서 사용할 Switch와 LED, 7Segment, KEY, JA Pmod, JB Pmod를 보드의 핀과 연결해주기 위해 xdc파일을 다음과 같이 수정하였다.

# Switches
set_property PACKAGE_PIN V17 [get_ports {sw[0]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {sw[0]}] 
						ː
set_property PACKAGE_PIN T1 [get_ports {sw[14]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {sw[14]}]
set_property PACKAGE_PIN R2 [get_ports RESET]
	set_property IOSTANDARD LVCMOS33 [get_ports RESET]
# LEDs
set_property PACKAGE_PIN U16 [get_ports {LED[0]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {LED[0]}] 
						ː
set_property PACKAGE_PIN L1 [get_ports {LED[15]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {LED[15]}]
#7 segment display
set_property PACKAGE_PIN W7 [get_ports {seg[0]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {seg[0]}]
						ː
set_property PACKAGE_PIN U7 [get_ports {seg[6]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {seg[6]}]
set_property PACKAGE_PIN V7 [get_ports dp]
	set_property IOSTANDARD LVCMOS33 [get_ports dp]
set_property PACKAGE_PIN U2 [get_ports {an[0]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {an[0]}]
						ː
set_property PACKAGE_PIN W4 [get_ports {an[3]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {an[3]}]
#Buttons
set_property PACKAGE_PIN T18 [get_ports KEY[0]]
	set_property IOSTANDARD LVCMOS33 [get_ports KEY[0]] 
						ː
set_property PACKAGE_PIN U18 [get_ports KEY[4]]
	set_property IOSTANDARD LVCMOS33 [get_ports KEY[4]]
#Pmod Header JA
set_property PACKAGE_PIN L2 [get_ports JATx]
	set_property IOSTANDARD LVCMOS33 [get_ports JATx] 
set_property PACKAGE_PIN J2 [get_ports JARx]
	set_property IOSTANDARD LVCMOS33 [get_ports JARx]
#Pmod Header JB
set_property PACKAGE_PIN A14 [get_ports {JBI[0]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {JBI[0]}] 
						ː
set_property PACKAGE_PIN B16 [get_ports {JBI[3]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {JBI[3]}]
set_property PACKAGE_PIN A15 [get_ports {JBO[0]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {JBO[0]}]
						ː
set_property PACKAGE_PIN C16 [get_ports {JBO[3]}]
	set_property IOSTANDARD LVCMOS33 [get_ports {JBO[3]}]

주석 처리가 되어 있는 핀을 활성화함으로써 핀을 사용한다. 또한 JA Pmod에서 JA2, JA3 핀은 각각 JATx, JARx로 수정하여 UART 통신에 사용한다.

  • 사용 모듈

🔌 BUS Module 정의

Basys3 보드의 UART 통신을 구성하기 위해 버스 모듈에서 UART의 input, output을 위와 같이 정의해준다.

module AHBLITE_SYS(
  //CLOCKS & RESET
  input 	wire CLK, 
  input 	wire RESET,
  
  //TO BOARD LEDs
  output 	wire 	[15:0]	LED,
  
  // SW
  input 	wire 	[14:0] 	sw,
  
  // KEY
  input 	wire 	[4:0] 	KEY,
  
  // SEG 
  output 	wire 	[6:0] 	seg, 
  output 	wire 	[3:0] 	an,
  output 	wire 			dp,
  
  // UART
  input 	wire 			JARx, 
  output 	wire 			JATx,
  
  // GPIO JB
  input 	wire 	[3:0] 	JBI, 
  output 	wire 	[3:0] 	JBO
);

xdc파일에서 수정한 Pmod 포트들과 연결이 되고 서로 이름이 같아야 한다.

📡 내부 신호 정의

wire를 활용하여 SLAVE READ DATA 및 SLAVE HREADYOUT 정의하였고, DCD와 MUX에 연결해주었다.

//SELECT SIGNALS
wire	[3:0]	MUX_SEL;
wire 			HSEL_MEM;
wire 			HSEL_GPIO;
wire			HSEL_KEY;
wire			HSEL_SEG;
wire 			HSEL_TIMER;
wire 			HSEL_UART;
wire 			HSEL_GPIO_JB; 

🚦 Interrupt 신호 정의

인터럽트 신호는 Key, Timer, UART로부터 발생하며 데이터를 처리하기 때문에, 이를 16비트 IRQ로 정의하여 Interrupt 신호를 받을 수 있도록 하였다.

wire 	KEY_IRQ; 
wire 	TIMER_IRQ; 
wire 	UART_IRQ;

//CM0-DS INTERRUPT SIGNALS
assign 	IRQ = {13'b0000_0000_0000_000, KEY_IRQ, UART_IRQ, TIMER_IRQ};
assign 	LED[15] = LOCKUP;
assign 	HRESETn = ~RESET;

🧰 Interrupt가 추가된 Module

Basys3에 내장되어 있는 SW와 LED를 사용하기 위해 GPIO 모듈을 정의하였다.

AHBGPIO uAHBGPIO( 
  //AHBLITE Signals
  .HSEL(HSEL_GPIO),
  			ː
  .HRDATA(HRDATA_GPIO), 
  .HREADYOUT(HREADYOUT_GPIO),
  
  .GPIOIN({1'b0, sw[14:0]}),
  .GPIOOUT(LED[14:0]) 
);

JB Pmod의 핀을 GPIO핀으로 사용하여 외부 장치에 입력을 받고 출력을 보낼 수 있도록 설정하였다. 각 입력과 출력은 4bit 신호로 사용하였으며, 이번 Project에서는 출력 1bit만 사용한다.

AHBGPIO uAHBGPIOJB( 
  //AHBLITE Signals
  .HSEL(HSEL_GPIO_JB), 
  			ː
  .HRDATA(HRDATA_GPIO_JB), 
  .HREADYOUT(HREADYOUT_GPIO_JB),
  
  .GPIOIN({12'b0, JBI[3:0]}),
  .GPIOOUT(JBO[3:0]) 
);

KEY를 사용하기 위해서 모듈을 버스에 붙였다. KEY에서 발생한 Interrupt 신호를 받기 위해서 Interrupt 신호도 연결되어 있다.

AHB2KEY2 uAHB2KEY2( 
  //AHBLITE Signals
  .HSEL(HSEL_KEY),
              ː
  .HRDATA(HRDATA_KEY), 
  .HREADYOUT(HREADYOUT_KEY), 
  
  //Sideband Signals 
  .KEY_IRQ(KEY_IRQ),
  .KEY(KEY) 
);

시스템에서 사용하는 업카운터와 스탑워치를 사용하기 위해서 타이머 모듈을 버스에 붙였으며, Interrupt 처리를 위한 신호가 연결되어 있다.

//AHBLite Slave 
AHBTIMER uAHBTIMER(
  //AHBLITE Signals 
  .HSEL(HSEL_TIMER),
              ː
  .HRDATA(HRDATA_TIMER), 
  .HREADYOUT(HREADYOUT_TIMER), 

  //Sideband Signals 
  .timer_irq(TIMER_IRQ)
);

UART를 통해 외부의 로봇과 통신을 하기 위해서 버스에 UART 모듈을 붙였다. 신호는 Rx, Tx를 사용하며, 이는 JA Pmod와 연결이 되어 있다. 또한, Interrupt 처리를 위한 신호가 연결되어 있다.

AHBUART uAHBUART( 
  //AHBLITE Signals
  .HSEL(HSEL_UART),
              ː
  .HRDATA(HRDATA_UART), 
  .HREADYOUT(HREADYOUT_UART), 

  //Sideband Signals
  .JARx(JARx),
  .JATx(JATx), 
  .uart_irq(UART_IRQ)
);

AHBUART 하위 파일 중 BAUDGEN 모듈을 살펴보면, UART 통신에 있어 중요한 통신 속도가 기재되어 있는 것을 확인할 수 있다. 코드 중간에 Baudrate에 주석이 존재하며 여기서 통신 속도를 19200으로 설정하였다는 안내가 있어, 이를 바탕으로 ESP32-WROOM-32D과의 원활한 UART 통신이 가능하다.

module BAUDGEN(
  input wire clk,
  input wire resetn, 
  output wire baudtick
);

reg [21:0] count_reg; 
wire [21:0] count_next;

//Counter
always @ (posedge clk, negedge resetn)
  begin 
    if(!resetn)
      count_reg <= 0;
    else
      count_reg <= count_next; 
end

//Baudrate = 19200 = 50Mhz/(163*16)
assign count_next = ((count_reg == 162) ? 0 : count_reg + 1'b1);
assign baudtick = ((count_reg == 162) ? 1'b1 : 1'b0); 

endmodule

🛎️ KEY Module 수정

기존 KEY 모듈이 4개의 key만 사용할 수 있었기에, 5개를 사용하기 위해 모듈을 수정하였다.

module AHB2KEY2(
					ː 
    input wire [4:0] KEY
  );

					ː
reg [4:0] keyout;  //key output
					ː

// sequential part
  always @(posedge HCLK or negedge HRESETn) 
  begin
    if(!HRESETn) 
    begin
      keyout <= 5'b0;
      key_pushed <= 1'b0; 
    end
    
    else if ((key_pushed == 1'b0) && (KEY != 5'b0)) 
    begin
      keyout <= KEY;
      key_pushed <= 1'b1;
    end
    
    else if (KEY == 5'b0) 
    begin
      keyout <= 5'b0;
      key_pushed <= 1'b0; 
    end
  end
    
// sequential part
  always @(posedge HCLK or negedge HRESETn) 
  begin
    if(!HRESETn) 
      KEY_IRQ <= 1'b0;
    else if ((key_pushed == 1'b0) && (KEY != 5'b0))     
      KEY_IRQ <= 1'b1;
    else if (rd)
      KEY_IRQ <= 1'b0;
  end
endmodule
ː

💿 S/W 구성

🔧 cm0dsasm.s

프로젝트에서 S/W 구성할 때, 어셈블리 언어만 사용하여 S/W를 구성하는 것이 아닌 C 언어도 함께 사용하기 위해, 위와 같이 어셈블리어를 수정하였다.

Reset_Handler가 작동하면 C언어의 main으로 이동한다.

					ː
;Reset Handler 
Reset_Handler	PROC
				GLOBAL Reset_Handler
				ENTRY 
          IMPORT __main
				LDR		R0, =__main
                BX		R0 				;Branch to __main
                ENDP
					ː

Interrupt가 발생했을 때, 처리해줄 ISR의 주소를 exception vector에 정의해줬다.

					ː
		;External Interrupts
		DCD	 			Timer_Handler 
		DCD 			UART_Handler 
		DCD 			Key_Handler
					ː

아래는 KEY Interrupt 핸들러이며, Interrupt가 발생했을 때 Interrupt를 처리하는 함수를 담고 있다. 여기서는 C로 작성한 코드를 사용하기 위해 context switching을 해주었다.

나머지 Timer_Handler, UART_Handler도 동일하게 작성하였으며, 여기서는 생략하였다.

					ː
Key_Handler		PROC
				EXPORT Key_Handler
								IMPORT Key_ISR
				PUSH	{R0, R1, R2, R3, LR}
                					BL Key_ISR
				POP 	{R0, R1, R2, R3, PC} 	;return
				ENDP
					ː

🧱 edk.driver.h

각각의 주변 장치를 컨트롤하기 위해 주솟값을 정의해줬다.

구조체를 사용하여 접근할 수 있도록 코드를 작성하였으며, 이 부분은 생략하였다.

					ː
#define AHB_GPIO_JB_BASE 	0x50000000
#define AHB_UART_BASE 		0x51000000
#define AHB_TIMER_BASE 		0x52000000
#define AHB_GPIO_BASE 		0x53000000
#define AHB_7SEG_BASE 		0x54000000
#define AHB_KEY_BASE 		0x55000000
#define NVIC_INT_ENABLE 	0xE000E100
					ː

edk.driver.c에는 각 driver를 컨트롤하기 위한 함수를 정의하였으며 함수들은 위와 같다. 7Segment에 값을 쓸 수 있는 함수, timer 관련 초기화, 활성화 인터럽트 클리어 함수가 있다.

					ː
//Write to 7-segment display 
void seven_seg_write(char dig1, char dig2,char dig3,char dig4); 
//Initialize the timer
void timer_init(int load_value, int prescale, int mode); 
//Enable the timer
void timer_enable(void); 
//Clear interrupt request from timer
void timer_irq_clear(void);
//GPIO read (from switches)
int GPIO_read(void);
//GPIO write (to LEDs)
void GPIO_write(int data); 
int GPIO_JB_read(void);
void GPIO_JB_write(int data);
					ː

GPIO는 4개의 함수가 있으며 읽고 쓸 수 있게 하는 함수이다. 그 중 GPIO_JB는 JB Pmod를 GPIO로 사용하여 읽고 쓸 수 있는 함수이다.

🚪 main.c - 헤더부분

사용하는 파일을 include해줬다. “”는 파일이며, <>는 라이브러리이다. <stdlib.h>, <time.h>는 시스템에서 랜덤값을 시간을 seed로 만들어주기 위해서 import하였다.

또한, TImer를 50000000 주기로 사용하기 위해서 아래와 같이 정의해줬다.

#include "EDK_CM0.h" 
#include "core_cm0.h" 
#include "edk_driver.h" 
#include <stdlib.h> 
#include <time.h>

#define Timer_Interrput_Frequency 	1 
#define System_Tick_Frequency 		50000000
#define Timer_Prescaler 			1 
#define Timer_Load_Value 
(System_Tick_Frequency/Timer_Interrput_Frequency/Timer_Prescaler)

SW[4:0]은 모드를 나타낸다. 이는 스위치에서 활성화된 스위치를 기반으로 작성하였다.

// MODE (SW)
#define MODE_UPCOUNTER 			0x01 
#define MODE_STOPWATCH 			0x02 
#define MODE_RANDOM_MULTIPLIER 	0x04 
#define MODE_ADDER_SUB 			0x08 
#define MODE_ROBOT 				0x10

시스템에서 사용한 함수들을 헤더 부분에 작성해주었다. 이 함수들의 역할은 다음과 같다.

// Function
void delay(unsigned int);
//period 변수 하나를 받아 반복문을 사용해 그만큼 지연시키는 함수
unsigned int knowBit (unsigned int, unsigned int); 
//temp 변수에서 [where] bit에 있는 bit를 추출하는 함수 checkMoreThanTwoSW 
unsigned int checkMoreThanTwoSW (unsigned int); 
//스위치가 0개나 2개 이상 켜져 있음을 감지하는 함수
unsigned int cut0to4(unsigned int);
//temp의 [4:0]를 추출하는 함수
unsigned int cut5to9(unsigned int);
//temp의 [9:5]를 추출하는 함수
unsigned int cut10to15(unsigned int);
//temp의 [15:10]를 추출하는 함수
void eachSegmentDivider(unsigned int, unsigned int); 
//result 변수를 세그먼트에 넣을 수 있도록 각 자리로 나눠서 넣어주는 함수, 1000의 자리는 임의의 값으로 선택할 수 있다.
void eachSegmentDivider_all(unsigned int);
//result 변수를 7세그먼트에 넣을 수 있도록 각 자리로 나눠서 넣어주는 함수
void digReset(void);
//dig 변수를 초기화해주는 함수
void diggReset(void);
//digg 변수를 초기화해주는 함수
void inverseBit(unsigned int*);
//temp가 1이면 0으로 0이면 1로 바꿔주는 함수
void increase_dig_4bit(void);
//4자리의 dig 변수를 하나씩 증가하는 함수
void decrease_dig_4bit(void);
//4자리의 dig 변수를 하나씩 감소하는 함수
void increase_dig_3bit(void);
//3자리의 dig 변수를 하나씩 증가하는 함수
void decrease_dig_3bit(void);
//3자리의 dig 변수를 하나씩 감소하는 함수

전역 변수의 값들로 역할은 다음과 같다.

// value
static char dig1,dig2,dig3,dig4;
//업 카운터의 카운팅 값
static char digg1,digg2,digg3,digg4;
//스탑워치의 카운팅 값
static char seven1000, seven100, seven10, seven1;
시스템에서 계산된 값을 7segment에 적기 전에 저장하는 변수
static unsigned int result;
//시스템에서 계산된 값을 저장하는 변수
static unsigned int stopwatchStart;
//스탑워치를 작동할지 멈출지를 결정하는 변수
static unsigned int add_sub_select; 
//덧셈과 뺄셈 모드를 결정하는 변수 (0: add mode, 1: sub mode)
static unsigned int stop_multi_select; 
//스탑워치와 곱셈 모드를 결정하는 변수 (0: stopwatch mode, 1: multiplier mode)

🪟 main.c - ISR 관련 부분

UART_ISR 함수이다. UART의 interrupt를 처리하기 위해서 정의했다. 여기서는 SW[4]의 값을 UART로 보내주는 역할을 한다.

void UART_ISR(void){
  *(unsigned char*) AHB_UART_BASE = knowBit(GPIO_read(),4);
}

Timer interrupt가 발생하면 우선 dig가 하나 증가하고 UPCOUNTER 모드하면 이를 7세그먼트로 출력한다. 만약 STOPWATCH 모드라면 반대로 숫자를 하나씩 감소해주는 기능을 수행한다.

STOPWATCH 모드에서 0이 되면 GPIO_JB 핀을 통해 외부 LED가 점등을 하게 된다. 만약 digg가 "0000"이 아니라면 GPIO_JB 핀의 출력을 0으로 초기화해 LED를 꺼준다. 그 후 timer의 인터럽트 신호를 clear해준다.

void Timer_ISR(void){ 
  increase_dig_4bit();

  // MODE_UPCOUNTER
  if(cut0to4(GPIO_read()) == MODE_UPCOUNTER) {
  	seven_seg_write(dig1, dig2, dig3, dig4);
  }
  
  // MODE_STOPWATCH
  if(cut0to4(GPIO_read()) == MODE_STOPWATCH) {
  	decrease_dig_4bit();
  	seven_seg_write(digg1, digg2, digg3, digg4);
  }
  
  // clear JB when not digg:0000
  if(((digg4==0) & (digg3==0) & (digg2==0) & (digg1==0)) != 1) { 
  // 1 is digg all 0000
  GPIO_JB_write(0x00);
  } 
  
  timer_irq_clear();
}

Key interrupt가 발생하면 5개의 물리적 Key 중에서 어느 Key를 눌렀는지 확인해야 한다. 그렇기 때문에 if문을 사용하여 각 5개의 물리적 Key가 눌렸을 때의 기능을 구현하였다.

void Key_ISR(){
  unsigned int temp;
  temp = *(unsigned int*) AHB_KEY_BASE;
  
  // top key: up-counter reset, stopwatch reset 
  if (temp == 0x1){ 
    digReset();
    diggReset(); 
    seven_seg_write(0,0,0,0); 
    stopwatchStart = 0;
  }
  // 업카운터와 스탑워치를 초기화하는 버튼으로 이에 대한 기능을 수행한다.
  //dig와 digg변수를 초기화해주고 7세그먼트를 0000으로 초기화해준다. 
  //스탑워치 작동 변수 stopwatchStart를 0으로 초기화 시켜줌으로써 스탑워치를 정지시킨다.
  
  // left key
  else if (temp == 0x2){ 
    increase_dig_3bit();
    seven_seg_write(digg1, digg2, digg3, digg4);
  }
  //스탑워치의 시간을 세팅하는데, 그 중 10의 자리를 증가시킨다. digg3를 하나 증가시켜준다.
  
  // right key
  else if (temp == 0x4){ 
    decrease_dig_3bit();
    seven_seg_write(digg1, digg2, digg3, digg4);
  }
  //스탑워치의 시간을 세팅하는데, 그 중 10의 자리를 감소시킨다. digg3를 하나 감소시켜준다.
  
  // bottom key 
  else if (temp == 0x8){ 
  	inverseBit(&stopwatchStart);
  }
  //스탑워치의 작동을 시작하거나 일시중지하는데, 이는 비트를 뒤집어주는 함수를 통해 수행된다. 
  //stop_multi_select가 0이면 작동을 하지 않는 상태이고, 1이면 작동을 하는 상태를 나타낸다.
  
  // middle key
  else if (temp == 0x10){ 
    inverseBit(&add_sub_select); 		 
    inverseBit(&stop_multi_select);
  }
  //덧셈기/뺄셈기 모드와 랜덤 숫자 발생기/곱셈기 모드일 때, 모드를 변경시켜주며, 이는 비트를 뒤집어 주는 함수를 통해 수행된다. 
  //예를 들어 0이면 덧셈기 모드, 1이면 뺄셈기 모드이다.
}

🧾 main.c [main()]

main함수가 실행되면 기본적으로 초기화를 해준다. Timer의 Timer_Load_Value, Timer_Prescaler를 초기화해주고 타이머를 할성화해준다. 또한, 7세그먼트를 0으로 초기화, JB Pmod의 출력신호를 0으로 초기화, 변수 초기화 작업을 해준다.

이후, 인터럽트 신호의 우선순위를 설정하고 활성화하여 인터럽트를 받을 수 있는 상태로 만들어준다.

int main(void){
  //Initialise timer (load value, prescaler value, mode value) 
  timer_init(Timer_Load_Value,Timer_Prescaler,1); 
  timer_enable();
  seven_seg_write(0,0,0,0);
  GPIO_JB_write(0x00); 					//Initialise GPIO_JB 
  stopwatchStart = 0;
  add_sub_select=0;
  stop_multi_select=0;

  NVIC_SetPriority (Timer_IRQn, 0x00); 
  NVIC_SetPriority (UART_IRQn, 0x80); 
  NVIC_SetPriority (KEY_IRQn, 0xC0); 
  NVIC_EnableIRQ(Timer_IRQn);			//enable timer interrupt
  NVIC_EnableIRQ(UART_IRQn); 			//enable UART interrupt
  NVIC_EnableIRQ(KEY_IRQn);				//enable UART interrupt
								ː

main 내부에는 while문이 존재해 프로그램이 종료되지 않고 계속 Polling을 통해 서비스를 처리할 수 있도록 하였다. GPIO_write(GPIO_read());는 basys3 보드 내부의 sw를 읽어 내부 led에 적도록 하기 위해 사용되었다.

while(1){
                    ː 
  GPIO_write(GPIO_read());
  if (checkMoreThanTwoSW(GPIO_read()) == 0) 
    goto nothingHappen;
                    ː
    nothingHappen: delay(1000000); // ******
  }
}

checkMoreThanTwoSW(GPIO_read()) 함수는 sw[4:0] 모드 스위치가 만약 0개 켜있거나 2이상 켜있을 경우 0을 반환하는데, 0을 반환한 경우 nothingHappen: 라벨로 건너뛰어서 다른 기능을 수행하지 못하게 하였다.

만약 sw가 0이거나 2이상일 경우 시스템이 작동을 하게 되면 프로그램이 꼬이기 때문이다.

모드 스위치가 정상적으로 1개만 켜있다면 goto문은 실행되지 않고 아래의 코드를 계속 수행한다.

                    ː
// MODE_RANDOM_MULTIPLIER
  if (cut0to4(GPIO_read()) == MODE_RANDOM_MULTIPLIER ) { 
  // RANDOM MODE
  if(stop_multi_select==0){ 
    result = rand() % 10000; 
    eachSegmentDivider_all(result); 
    seven_seg_write(seven1000, seven100, seven10, seven1);
  }
  
  // MULTIPLIER MODE
  else { 
    result = cut5to9(GPIO_read()) * cut10to15(GPIO_read()); 
    eachSegmentDivider(result, 0x00); 
    seven_seg_write(seven1000, seven100, seven10, seven1);
  } 
}
                    ː
  • sw[2]가 켜진 경우, 랜덤 숫자를 출력하거나 곱셈기로 동작한다.
    stop_multi_select 변수에 따라 랜덤 숫자를 출력 하거나 곱셈기로 동작하는데, stop_multi_select 변수는 middle-key를 누를 때마다 바뀐다.

  • stop_multi_select가 0이면 랜덤 숫자를 출력한다.
    c함수 stdlib.h 라이브러리 내부의 rand()함수를 이용해 난 수 값을 만들어 줬다. 이때 seed가 시간이므로 time.h도 include를 해줘야 한다. 이렇게 만든 변수값을 % 10000을 통해 4자리의 값으로 만들어 주었고 7세그먼트에 이를 출력한다.

  • stop_multi_select가 1이면 곱셈기로 동작한다. cut5to9(GPIO_read())를 통해 sw[9:5]의 값을 추출하고 cut10to15(GPIO_read())를 통해 sw[15:10] 값을 추출하여 곱해주며ㅡ 이를 7세그먼트에 출력한다.

                    		ː
// MODE_ADDER_SUB
else if (cut0to4(GPIO_read()) == MODE_ADDER_SUB){ 
  // ADDER MODE
  if(add_sub_select==0){ 
    result = cut5to9(GPIO_read()) + cut10to15(GPIO_read()); 
    eachSegmentDivider(result, 0x00); 
    seven_seg_write(seven1000, seven100, seven10, seven1);
  }

  // SUB MODE
  else{ 
    if(cut10to15(GPIO_read())> cut5to9(GPIO_read())){
      result = cut10to15(GPIO_read()) - cut5to9(GPIO_read()); 
      eachSegmentDivider(result, 0x00); 
      seven_seg_write(seven1000, seven100, seven10, seven1);
    }

    else if(cut10to15(GPIO_read())< cut5to9(GPIO_read())){
      result = cut5to9(GPIO_read()) - cut10to15(GPIO_read()); 
      eachSegmentDivider(result, 0x11); // 0x11 is - 
      seven_seg_write(seven1000, seven100, seven10, seven1);
    } 

    else{
    seven_seg_write(0,0,0,0);
    } 
  }
}
                    		ː
  • sw[3]이 켜진 경우, 덧셈기나 뺄셈기로 동작한다.
    add_sub_select 변수에 따라 덧셈기나 뺄셈기로 동작하는데, add_sub_select 변수는 middle-key를 누를 때마다 바뀐다.

  • add_sub_select 0이면 덧셈기로 동작한다.
    cut5to9(GPIO_read())를 통해 sw[9:5]의 값을 추출하고 cut10to15(GPIO_read())를 통해 sw[15:10] 값을 추출하여 더해준다. 더해준 값을 7세그먼트에 출력한다.

  • add_sub_select 1이면 뺄셈기로 동작한다.
    cut5to9(GPIO_read())를 통해 sw[9:5]의 값을 추출하고 cut10to15(GPIO_read())를 통해 sw[15:10] 값을 추출하여 빼주고, 이를 7세그먼트에 출력한다.

뺄셈기일 경우 if문을 사용하여 처리를 해주었는데, A-B에서 A가 B보다 크면 양수가 나와 문제가 없지만 B가 A보다 클 경우 (-)를 디스플레이해줘야 하기에 if문으로 처리해주었다.

                    		ː
// MODE_ROBOT
else if (cut0to4(GPIO_read()) == MODE_ROBOT){ 
	seven_seg_write(0x11,0x11,0x11,0x11);
}
                    		ː
  • sw[4]가 켜진 경우, ROBOT mode로 동작한다.
    이때는 기본적으로 UART의 interrupt로 처리하며, 위 코드는 ROBOT mode로 동작함을 알려주기 위해 7세그먼트에 (----)를 표시하는 코드이다.

📝 ESP32_Servo_UART.ino

Basys3와 UART 통신을 위하여 ESP32에서도 동일하게 UART 통신을 위한 구성을 하였다.

Serial2.begin(19200, SERIAL_8N1, 16, 17);을 통해 16번의 RX 핀과 17번의 TX 핀을 Serial2 통신으로 구성하였으며, Basys3에서 설정한 통신 속도(Baudrate)가 19200이였기에 ESP32에서도 동일하게 설정하였다.

또한 Basys3의 UART 통신에 관한 래퍼런스 매뉴얼을 참고하면 페러티 bit이 존재하지 않는 것을 알 수 있기에 ESP32의 통신 설정에서도 SERIAL_8N1 모 드를 사용하여 통신을 진행하였다.

#include <ESP32_Servo.h> 
Servo frontLegs, backLegs;

int FRONT_Servo = 2; 
int BACK_Servo = 4;

void setup() {
  Serial.begin(19200);
  Serial2.begin(19200, SERIAL_8N1, 16, 17);
  
  frontLegs.attach(FRONT_Servo); 
  backLegs.attach(BACK_Servo); 
  frontLegs.write(90); //Center the servo 
  backLegs.write(90); //Center the servo
  
  delay(5000); //Wait 5 sec to fix bug legs 
}

void loop() {
  if (Serial2.available()) {
  	Serial.println(Serial2.read());
    
    if (Serial2.read() == 1) 
    Servo_Work();
    else 
    Servo_Wait();
  } 
}
  
void Servo_Work() {
  delay(200);
  
  frontLegs.write(70); //move front leg a little delay(200);
  backLegs.write(70); //move back leg a little delay(200);
  frontLegs.write(110); //move front leg a little delay(200);
  backLegs.write(110); //move back leg a little
}
  
void Servo_Wait() {
  frontLegs.write(90); //Center the servo 
  backLegs.write(90); //Center the servo
}

5. 동작 실험 및 고찰

📎 각 기능별 동작 실험

  • Up Counter

  • 스탑워치

  • 랜덤숫자/곱셈기

  • 덧셈기/뺄셈기

  • 로봇 컨트롤

ESP32의 코드를 구현한 Arduino IDE에서 아래의 코드를 통해 Basys3와의 UART 통신 결과값을 받아볼 수 있었으며, Basys3의 SW4가 활성화됨에 따라 들어오는 값이 0 또는 1로 변경된다는 것을 알 수 있었다.

//ESP32_Servo_UART.ino

void loop() {
  if (Serial2.available()) {
  	Serial.println(Serial2.read());
				:
}

Basys3 SW4의 활성화 여부에 따른 UART 통신 결과값 확인

이를 이용하여 서보 모터를 0 또는 1의 값에 의해 제어하였으며, 이 조건문을 통해 아래의 로봇을 제어할 수 있게 되었다.

  • 로봇 H/W 구성

  • 로봇 동작 확인

✏️ 최종 Project에 대한 고찰

다기능 카운터, 계산기와 UART 통신을 통한 작은 로봇 제어를 설계하였다. 모든 결과가 설계대로 잘 작동함을 확인할 수 있었다.

프로젝트를 수행하며 ARM HW에서 모듈과 버스의 관계에 대한 이해가 향상되었으며, 각 모듈에 대한 사용법을 확실히 익힐 수 있었다.

또한, memory mapped I/O를 이해해 외부 장치 컨트롤에 대한 이해가 높아졌고 외부 통신 방법에 대한 이해가 높아졌다.

프로젝트를 수행하며 각자의 역할은 다음과 같이 나누어 수행을 했다.

  • An (sunhong-dev.tistory)
    Verilog ARM Cortex-M0 HW 구성, 시스템의 SW 구현

  • Hwang
    Verilog ARM Cortex-M0 HW 구성, ESP32-WROOM-32D 회로 구성 및 코드 작성, 로봇 구현


6. 참고 자료 및 문헌

0개의 댓글