[SoC Review]
📌 SoC 개요
우리가 만든 선풍기같은 것도 시스템이지 않느냐?
시스템은 '계'라는 뜻이니, 맞기는 하다.
하지만 SoC에서의 시스템은 다른 의미이다.
CPU, RAM 등 컴퓨터 시스템을 의미한다는 것
컴퓨터 자체를 칩 안에 집어 넣었다는 뜻
모바일 기술 발전을 가장 막고 있는 것이 바로 배터리 성능
CPU는 얼마든지 더 성능을 올릴 수 있다.
AP 같은 것들을 더 좋은 걸 쓰면 됨
근데 그러면 1~2시간만에 배터리 다 소모됨.
시스템에 다 넣는 것은 문제가 아닌다.
IoT라던가, 가전제품이라던가, 예전에는 시스템을 쓰지 않았던 기기들이
요즘에는 시스템을 다 쓰기 시작했다.
TV나 휴대폰 뜯어보면, 기판 하나 들어있고 끝. → System on a Chip
그런 기기들의 요구가 점점 늘어날수록 SoC에 대한 수요가 더욱 더 늘어날 수 밖에 없다.
자체적으로 생산하는 회사도 많아졌고 그만큼 인력 수요도 같이 늘어났다.
📌 억까의 가능성
코드 작성 시, Wrapping, Implementation 중 어느 단계에서도 문제는 발생할 수 있다.
아직까지 Vivado Vitis가 완벽하지는 않다.
Xilinx는 Chip 만드는 회사이므로 소프트웨어적으로 완벽하지 않아.
우리는 하드웨어를 만들고 있으니, 그냥 억까로 안 될 수도 있다는 것을 알고 있자.
📌 Memory Mapped I/O
cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
ㆍ IP들은 메모리도 아닌데, 왜 주소가 필요할까?
우리가 CPU만들 때 버스 라인이 있던 것을 기억해보자.
주변기기들끼리 연결된 버스가 있다.
이 것도 제어를 해줘야 해. 이 기능을 하는 것이 AXI Interconnect
GPIO에 접근할 때, 메모리로 접근하게 된다.
그래서 주소 방식으로 접근을 하게 됨.
대신 GPIO에 접근할 때는 메모리 주소가 아니라 따로 각각의 주소가 있다.
예시)
GPIO에 1111을 줘라. 라는 명령을 AXI Interconnect에 전달
AXI Interconnect가 해당 GPIO에 맞는 주소에 1111을 전달
GPIO에서 1111 출력
CPU 입장에서는 그냥 다 메모리
하지만 시스템에서 동작하는 것은 GPIO
UART도 마찬가지로 CPU 입장에서는 메모리
우리가 쓰고자 하는 것을 UDR0에 쓰면 이 것을 한 비트씩 통신 모듈에서 출력
이렇게 하는 것을 Memory Mapped I/O 라고 한다.
그래서 다 주소가 있는 것
절차 지향 프로그래밍을 하고 있지만, 약간의 객체를 만들 수 있다.
[FND Control]
📌 코드 최적화
입력값이 변경됐을 때만 동작하도록 코드를 최적화시켜주자.
이렇게 해주면, 시스템 부하를 줄여 동작을 빠르게 해줄 수 있다.
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
#define LED_ID XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL 1
#define SWITCH_CHANNEL 1
#define FND_COM_CHANNEL 1
#define FND_SEG7_CHANNEL 2
int main()
{
init_platform();
print("Start!\n\r");
XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
XGpio led_device; // gpio 객체
XGpio switch_device; // gpio 객체
XGpio fnd_device; // gpio 객체
u32 data = 0;
u32 old_data = 0;
u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
unsigned int FND[4]; // 여기다 뭘 쓰면, 이 것이 FND에 출력되도록 작성
////////////////////////////////////// Initialize Led Device //////////////////////////////////////
/////// LED ///////
cfg_ptr_led = XGpio_LookupConfig(LED_ID);
XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
// XGpio_CfgInitialize(InstancePtr, Config, EffectiveAddr)
// XGpio_SetDataDirection(InstancePtr, Channel, DirectionMask)
//-----------------------------------------------------------------------------------------------//
/////// SWITCH ///////
cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
//-----------------------------------------------------------------------------------------------//
/////// FND ///////
cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
///////////////////////////////////////////////////////////////////////////////////////////////////
while(1)
{
// print("Hello World\n\r"); // UART Test
// led_data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
// XGpio_DiscreteWrite(&led_device, LED_CHANNEL, led_data);
// XGpio_DiscreteWrite(&fnd_device, FND_DISP_CHANNEL, fnd_data);
old_data = data;
data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
{
old_data = data;
XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
FND[0] = ~fnd_value[0xf & data];
FND[1] = ~fnd_value[(0xf & data>>4)];
FND[2] = ~fnd_value[(0xf & data>>8)];
FND[3] = ~fnd_value[(0xf & data>>12)];
// 이렇게 따로 코드를 분류해주면, 데이터가 바뀔 때만 갱신할 수 있다.
// 스위치 입력이 달라졌을 때만, LED 출력을 바꿔줌
// 시스템의 부하를 줄여, 좀 더 빠르게 동작시킬 수 있다.
}
for(int i = 0;i<4;i++)
{
// FND[i] = ~fnd_value[(0xf & data>>(i*4))];
XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, ~(1<<i));
// XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, ~fnd_value[(0xf & data>>(i*4))]);
XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
MB_Sleep(1);
}
}
cleanup_platform();
return 0;
}
📌 코드의 함수화
[함수로 만들어주는 이유]
코드의 가독성과 재사용성을 높이기 위해 함수로 만들어 주자.
고급 언어를 쓰는 이유가 바로 가독성때문인데, 그 것을 더 극대화해주기 위해 함수를 쓴다.
요즘에는 시스템이 하도 커져서 혼자서는 절대 못 해.
내가 알아보기 쉽게 하는 것은 물론, 팀원들도 알아보기 쉽게 작성해주는 것이 좋다.
→ 퇴근 시간을 더욱 빠르게!
void FND_update()
{
FND[0] = ~fnd_value[0xf & data];
FND[1] = ~fnd_value[(0xf & data>>4)];
FND[2] = ~fnd_value[(0xf & data>>8)];
FND[3] = ~fnd_value[(0xf & data>>12)];
}
///////////////////////////////////////////////////////////////////////
if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
{
old_data = data;
XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
FND_update();
}
📌 Counting Code
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
#define LED_ID XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL 1
#define SWITCH_CHANNEL 1
#define FND_COM_CHANNEL 1
#define FND_SEG7_CHANNEL 2
int main()
{
init_platform();
print("Start!\n\r");
XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
XGpio led_device; // gpio 객체
XGpio switch_device; // gpio 객체
XGpio fnd_device; // gpio 객체
u32 data = 0;
u32 old_data = 0;
u32 FND_data = 0;
u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
unsigned int FND[4]; // 여기다 뭘 쓰면, 이 것이 FND에 출력되도록 작성
void FND_update(unsigned data)
{
FND[0] = ~fnd_value[0xf & data];
FND[1] = ~fnd_value[(0xf & data>>4)];
FND[2] = ~fnd_value[(0xf & data>>8)];
FND[3] = ~fnd_value[(0xf & data>>12)];
return;
}
////////////////////////////////////// Initialize Led Device //////////////////////////////////////
/////// LED ///////
cfg_ptr_led = XGpio_LookupConfig(LED_ID);
XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
//-----------------------------------------------------------------------------------------------//
/////// SWITCH ///////
cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
//-----------------------------------------------------------------------------------------------//
/////// FND ///////
cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
///////////////////////////////////////////////////////////////////////////////////////////////////
while(1)
{
old_data = data;
data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
{
old_data = data;
XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
}
FND_update(FND_data++);
for(int i = 0;i<4;i++)
{
XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, ~(1<<i));
XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
MB_Sleep(1);
}
}
cleanup_platform();
return 0;
}
10진수로 바꿔보자.
void FND_update_dec(unsigned data)
{
FND[0] = ~fnd_value[data%10];
FND[1] = ~fnd_value[data/10%10];
FND[2] = ~fnd_value[data/100%10];
FND[3] = ~fnd_value[data/1000%10];
return;
}
///////////////////////////////////////////////
FND_update_dec(FND_data++);
📌 Porting
1. FND FONT
다른 보드를 쓸 때는 최상위비트가 다른 곳에 연결될 수도 있잖아.
그러면 다시 또 코드를 만들어 줘야 하는데, 그러면 재사용성이 떨어진다.
재사용성을 위해 폰트를 만들어 주자.
이러한 작업을 Porting이라 한다.
// FND 폰트를 위한 Define
#define FND_A 0
#define FND_B 1
#define FND_C 2
#define FND_D 3
#define FND_E 4
#define FND_F 5
#define FND_G 6
#define FND_P 7 // dot point
unsigned int FND_FONT[] = {
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 0<<FND_G | 0<<FND_P, // 0
1<<FND_B | 1<<FND_C, // 1
1<<FND_A | 1<<FND_B | 1<<FND_D | 1<<FND_E | 1<<FND_G, // 2
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_G, // 3
1<<FND_B | 1<<FND_C | 1<<FND_F | 1<<FND_G, // 4
1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, // 5
1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // 6
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_F, // 7
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // 8
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, // 9
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_E | 1<<FND_F | 1<<FND_G, // A
1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // B
1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F, // C
1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_G, // D
1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // E
1<<FND_A | 1<<FND_E | 1<<FND_F | 1<<FND_G // F
};
////////////////////////////////////////////////////////////////////////////////////////////////////
void FND_update_hex(unsigned data)
{
FND[0] = ~FND_FONT[0xf & data];
FND[1] = ~FND_FONT[(0xf & data>>4)];
FND[2] = ~FND_FONT[(0xf & data>>8)];
FND[3] = ~FND_FONT[(0xf & data>>12)];
return;
}
void FND_update_dec(unsigned data)
{
FND[0] = ~FND_FONT[data%10];
FND[1] = ~FND_FONT[data/10%10];
FND[2] = ~FND_FONT[data/100%10];
FND[3] = ~FND_FONT[data/1000%10];
return;
}
2. FND DIGIT
시프트 연산도 결국에는 연산
CPU가 계속 일해야 된다는 의미
굳이 그렇게 고생시킬 필요가 있나?
얘도 변수로 만들어줘서 필요할 때마다 불러오자
그러면 처음 변수 만들 때 한 번만 연산하고 그 이후에는 그냥 갖다 쓰면 됨
unsigned int FND_digit[] = {
~(1<<0),
~(1<<1),
~(1<<2),
~(1<<3),
};
얘도 Porting해주자.
// FND Digit을 위한 Define
#define FND_DIGIT_0 0
#define FND_DIGIT_10 1
#define FND_DIGIT_100 2
#define FND_DIGIT_1000 3
unsigned int FND_digit[] = {
~(1<<FND_DIGIT_0),
~(1<<FND_DIGIT_10),
~(1<<FND_DIGIT_100),
~(1<<FND_DIGIT_1000),
};
////////////////////////////////////////////////////////////////
XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, FND_digit[i]);
[Full Code]
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
#define LED_ID XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL 1
#define SWITCH_CHANNEL 1
#define FND_COM_CHANNEL 1
#define FND_SEG7_CHANNEL 2
// FND 폰트를 위한 Define
#define FND_A 0
#define FND_B 1
#define FND_C 2
#define FND_D 3
#define FND_E 4
#define FND_F 5
#define FND_G 6
#define FND_P 7 // dot point
// FND Digit을 위한 Define
#define FND_DIGIT_0 0
#define FND_DIGIT_10 1
#define FND_DIGIT_100 2
#define FND_DIGIT_1000 3
unsigned int FND_FONT[] = {
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 0<<FND_G | 0<<FND_P, // 0
1<<FND_B | 1<<FND_C, // 1
1<<FND_A | 1<<FND_B | 1<<FND_D | 1<<FND_E | 1<<FND_G, // 2
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_G, // 3
1<<FND_B | 1<<FND_C | 1<<FND_F | 1<<FND_G, // 4
1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, // 5
1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // 6
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_F, // 7
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // 8
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, // 9
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_E | 1<<FND_F | 1<<FND_G, // A
1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // B
1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F, // C
1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_G, // D
1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // E
1<<FND_A | 1<<FND_E | 1<<FND_F | 1<<FND_G // F
};
//unsigned int FND_digit[] = {
// ~(1<<0),
// ~(1<<1),
// ~(1<<2),
// ~(1<<3),
//};
unsigned int FND_digit[] = {
~(1<<FND_DIGIT_0),
~(1<<FND_DIGIT_10),
~(1<<FND_DIGIT_100),
~(1<<FND_DIGIT_1000),
};
int main()
{
init_platform();
print("Start!\n\r");
XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
XGpio led_device; // gpio 객체
XGpio switch_device; // gpio 객체
XGpio fnd_device; // gpio 객체
u32 data = 0;
u32 old_data = 0;
u32 FND_data = 0;
u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
unsigned int FND[4]; // 여기다 뭘 쓰면, 이 것이 FND에 출력되도록 작성
// void FND_update(unsigned data)
// {
// FND[0] = ~fnd_value[0xf & data];
// FND[1] = ~fnd_value[(0xf & data>>4)];
// FND[2] = ~fnd_value[(0xf & data>>8)];
// FND[3] = ~fnd_value[(0xf & data>>12)];
// return;
// }
void FND_update_hex(unsigned data)
{
FND[0] = ~FND_FONT[0xf & data];
FND[1] = ~FND_FONT[(0xf & data>>4)];
FND[2] = ~FND_FONT[(0xf & data>>8)];
FND[3] = ~FND_FONT[(0xf & data>>12)];
return;
}
void FND_update_dec(unsigned data)
{
FND[0] = ~FND_FONT[data%10];
FND[1] = ~FND_FONT[data/10%10];
FND[2] = ~FND_FONT[data/100%10];
FND[3] = ~FND_FONT[data/1000%10];
return;
}
////////////////////////////////////// Initialize Led Device //////////////////////////////////////
/////// LED ///////
cfg_ptr_led = XGpio_LookupConfig(LED_ID);
XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
// XGpio_CfgInitialize(InstancePtr, Config, EffectiveAddr)
// XGpio_SetDataDirection(InstancePtr, Channel, DirectionMask)
//-----------------------------------------------------------------------------------------------//
/////// SWITCH ///////
cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
//-----------------------------------------------------------------------------------------------//
/////// FND ///////
cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
///////////////////////////////////////////////////////////////////////////////////////////////////
while(1)
{
// print("Hello World\n\r"); // UART Test
// led_data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
// XGpio_DiscreteWrite(&led_device, LED_CHANNEL, led_data);
// XGpio_DiscreteWrite(&fnd_device, FND_DISP_CHANNEL, fnd_data);
old_data = data;
data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
{
old_data = data;
XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
// 이렇게 따로 코드를 분류해주면, 데이터가 바뀔 때만 갱신할 수 있다.
// 스위치 입력이 달라졌을 때만, LED 출력을 바꿔줌
// 시스템의 부하를 줄여, 좀 더 빠르게 동작시킬 수 있다.
}
FND_update_hex(FND_data++);
// FND_update_dec(FND_data++);
for(int i = 0;i<4;i++)
{
// FND[i] = ~fnd_value[(0xf & data>>(i*4))];
// XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, ~(1<<i));
XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, FND_digit[i]);
// XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, ~fnd_value[(0xf & data>>(i*4))]);
XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
MB_Sleep(1);
}
}
cleanup_platform();
return 0;
}
📌 헤더파일과 소스파일로 분할
ifndef : if not define
만약 define 되어 있지 않으면, endif까지 define해라.
이렇게 쓰면 한 번만 define되겠지?
헤더파일을 여기 저기에 넣어도 한 번만 실행하도록 하는 코드
FND.h
#ifndef FND_H_ // FND_H_ 가 Define되어 있지 않다면, endif까지 Define해라.
#define FND_H_ // 여기서 Define했으니, 이후 여기 저기에 넣어도 Define 안 됨.
////////////// Define //////////////
// FND 폰트를 위한 Define
#define FND_A 0
#define FND_B 1
#define FND_C 2
#define FND_D 3
#define FND_E 4
#define FND_F 5
#define FND_G 6
#define FND_P 7 // dot point
// FND Digit을 위한 Define
#define FND_DIGIT_0 0
#define FND_DIGIT_10 1
#define FND_DIGIT_100 2
#define FND_DIGIT_1000 3
//////////////// End /////////////////
////////////// Variable //////////////
unsigned int FND_FONT[];
unsigned int FND_digit[];
unsigned int FND[4];
//////////////// End /////////////////
////////////// Prototype //////////////
void FND_update_hex(unsigned data);
void FND_update_dec(unsigned data);
//////////////// End /////////////////
#endif
FND.c
#include "FND.h"
//////////////////////////////////////////// Init Variable //////////////////////////////////////////
unsigned int FND_FONT[] = {
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 0<<FND_G | 0<<FND_P, // 0
1<<FND_B | 1<<FND_C, // 1
1<<FND_A | 1<<FND_B | 1<<FND_D | 1<<FND_E | 1<<FND_G, // 2
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_G, // 3
1<<FND_B | 1<<FND_C | 1<<FND_F | 1<<FND_G, // 4
1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, // 5
1<<FND_A | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // 6
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_F, // 7
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // 8
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_F | 1<<FND_G, // 9
1<<FND_A | 1<<FND_B | 1<<FND_C | 1<<FND_E | 1<<FND_F | 1<<FND_G, // A
1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // B
1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F, // C
1<<FND_B | 1<<FND_C | 1<<FND_D | 1<<FND_E | 1<<FND_G, // D
1<<FND_A | 1<<FND_D | 1<<FND_E | 1<<FND_F | 1<<FND_G, // E
1<<FND_A | 1<<FND_E | 1<<FND_F | 1<<FND_G // F
};
unsigned int FND_digit[] = {
~(1<<FND_DIGIT_0),
~(1<<FND_DIGIT_10),
~(1<<FND_DIGIT_100),
~(1<<FND_DIGIT_1000),
};
///////////////////////////////////////////////// End ///////////////////////////////////////////////
//////////////////////////////////////////// Init Function //////////////////////////////////////////
void FND_update_hex(unsigned data)
{
FND[0] = ~FND_FONT[0xf & data];
FND[1] = ~FND_FONT[(0xf & data>>4)];
FND[2] = ~FND_FONT[(0xf & data>>8)];
FND[3] = ~FND_FONT[(0xf & data>>12)];
return;
}
void FND_update_dec(unsigned data)
{
FND[0] = ~FND_FONT[data%10];
FND[1] = ~FND_FONT[data/10%10];
FND[2] = ~FND_FONT[data/100%10];
FND[3] = ~FND_FONT[data/1000%10];
return;
}
///////////////////////////////////////////////// End ///////////////////////////////////////////////
helloworld.c
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
#include "FND.h"
#define LED_ID XPAR_AXI_GPIO_LED_DEVICE_ID
#define SWITCH_ID XPAR_AXI_GPIO_SWITCH_DEVICE_ID
#define FND_ID XPAR_AXI_GPIO_FND_DEVICE_ID
#define LED_CHANNEL 1
#define SWITCH_CHANNEL 1
#define FND_COM_CHANNEL 1
#define FND_SEG7_CHANNEL 2
int main()
{
init_platform();
print("Start!\n\r");
XGpio_Config *cfg_ptr_led; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_switch; // XGpio_Config 구조체의 주소
XGpio_Config *cfg_ptr_fnd; // XGpio_Config 구조체의 주소
XGpio led_device; // gpio 객체
XGpio switch_device; // gpio 객체
XGpio fnd_device; // gpio 객체
u32 data = 0;
u32 old_data = 0;
u32 FND_data = 0;
u8 fnd_value[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x67, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
////////////////////////////////////// Initialize Led Device //////////////////////////////////////
/////// LED ///////
cfg_ptr_led = XGpio_LookupConfig(LED_ID);
XGpio_CfgInitialize(&led_device, cfg_ptr_led, cfg_ptr_led->BaseAddress);
XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0); // 0을 줘서 출력 설정
//-----------------------------------------------------------------------------------------------//
/////// SWITCH ///////
cfg_ptr_switch = XGpio_LookupConfig(SWITCH_ID);
XGpio_CfgInitialize(&switch_device, cfg_ptr_switch, cfg_ptr_switch->BaseAddress);
XGpio_SetDataDirection(&switch_device, SWITCH_CHANNEL, 1); // 1을 줘서 입력 설정
//-----------------------------------------------------------------------------------------------//
/////// FND ///////
cfg_ptr_fnd = XGpio_LookupConfig(FND_ID);
XGpio_CfgInitialize(&fnd_device, cfg_ptr_fnd, cfg_ptr_fnd->BaseAddress);
XGpio_SetDataDirection(&fnd_device, FND_COM_CHANNEL, 0); // 0을 줘서 출력 설정
XGpio_SetDataDirection(&fnd_device, FND_SEG7_CHANNEL, 0); // 0을 줘서 출력 설정
///////////////////////////////////////////////////////////////////////////////////////////////////
while(1)
{
old_data = data;
data = XGpio_DiscreteRead(&switch_device, SWITCH_CHANNEL);
if(old_data != data) // FND의 출력을 바꿔줄 필요가 있을 때만 사용
{
old_data = data;
XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);
}
FND_update_hex(FND_data++);
// FND_update_dec(FND_data++);
for(int i = 0;i<4;i++)
{
XGpio_DiscreteWrite(&fnd_device, FND_COM_CHANNEL, FND_digit[i]);
XGpio_DiscreteWrite(&fnd_device, FND_SEG7_CHANNEL, FND[i]); // 코드로 가상화 해놓은 것
MB_Sleep(1);
}
}
cleanup_platform();
return 0;
}
[FND Controller module]
📌 나만의 IP 생성
우리가 만든 FND Cntr 모듈을 얹어서 사용해보자.
AXI Interconnect와 연결할 수 있는 단자를 만들어 줘야 한다.
새로 블록디자인을 만들어 주자.
이제까지는 이미 IP catalog에 만들어져 있는 IP를 썼지만,
이번에는 우리가 만든 모듈을 불러올 것이다.
IP : 지적 자산
이제 AXI 통신 모듈에 FND_Cntr 모듈을 붙여주면 된다.
FND Cntr 모듈을 가져와서 High 모듈에 붙여주자.
// Users to add ports here
output [3:0] com,
output [7:0] seg_7,
// User ports ends
// Do not modify the ports beyond this line
Value는 메모리로 받아야 한다(MMIO이기 때문, 그 주소에 대한 메모리를 넣어준다.)
// Implement memory mapped register select and read logic generation
// Slave register read enable is asserted when valid address is available
// and the slave is ready to accept the read address.
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
always @(*)
begin
// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
3'h0 : reg_data_out <= slv_reg0;
3'h1 : reg_data_out <= slv_reg1;
3'h2 : reg_data_out <= slv_reg2;
3'h3 : reg_data_out <= slv_reg3;
3'h4 : reg_data_out <= slv_reg4;
3'h5 : reg_data_out <= slv_reg5;
3'h6 : reg_data_out <= slv_reg6;
3'h7 : reg_data_out <= slv_reg7;
default : reg_data_out <= 0;
endcase
end
위 코드는 메모리에 대한 주소를 작성한 코드(메모리명 : slv_reg0~7)
이걸 눌러주면, 프로젝트가 꺼질 것이다.
이제 우리가 만든 IP를 불러와서 붙여주고, xdc 파일에서 연결만 해주면 된다.
출력 단자를 만들고 싶은 포트에 우클릭한 후, Make External 을 클릭해 출력 포트를 만들어 준다.
##7 Segment Display
set_property -dict { PACKAGE_PIN W7 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[0]}]
set_property -dict { PACKAGE_PIN W6 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[1]}]
set_property -dict { PACKAGE_PIN U8 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[2]}]
set_property -dict { PACKAGE_PIN V8 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[3]}]
set_property -dict { PACKAGE_PIN U5 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[4]}]
set_property -dict { PACKAGE_PIN V5 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[5]}]
set_property -dict { PACKAGE_PIN U7 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[6]}]
set_property -dict { PACKAGE_PIN V7 IOSTANDARD LVCMOS33 } [get_ports {seg_7_0[7]}]
set_property -dict { PACKAGE_PIN U2 IOSTANDARD LVCMOS33 } [get_ports {com_0[3]}]
set_property -dict { PACKAGE_PIN U4 IOSTANDARD LVCMOS33 } [get_ports {com_0[2]}]
set_property -dict { PACKAGE_PIN V4 IOSTANDARD LVCMOS33 } [get_ports {com_0[1]}]
set_property -dict { PACKAGE_PIN W4 IOSTANDARD LVCMOS33 } [get_ports {com_0[0]}]
xdc 파일에서 다 바꿔주자.
📌 Vitis Project 생성 및 Main code 작성
1. Export Hardware
2. Create Platform Project
3. Create Application Project
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"
int main()
{
init_platform();
print("Start!\n\r");
int data = 0;
// volatile unsigned int *FND_cntr = (volatile unsigned int *) XPAR_MYIP_FND_CNTR_0_S00_AXI_BASEADDR;
// 이렇게 줘도 되고, 아래처럼 줘도 된다.
volatile unsigned int *FND_cntr = (volatile unsigned int *) 0x44A00000;
// FND_cntr의 포인터변수를 volatile unsigned int * 라는 데이터타입으로 임시 변환
// *FND_cntr의 값은 0x44A00000
while(1)
{
FND_cntr[0] = data++; // [0] 배열은 포인터
// 온전성 검사(Sanity Check)해보자.
xil_printf("%X\n\r", FND_cntr[7]);
MB_Sleep(100);
}
cleanup_platform();
return 0;
}