FPGA 실습(3)
3. 다중 UART 구현
이전 글에 작성한 UART블럭을 여러개를 추가하고, UART baud rate가 다르도록 구성하여 보고 여러 포트에서 입력되는 인터럽트를 처리하는 과정을 살펴본다.
3.1요구사항
- UART port을 5개로 구성하고, 사용자의 설정에 따라 baud rate을 10M 혹은 115200으로 설정할 수 있게 한다.
3.2 구현
앞에서 설계한 설계화면의 Diagram Tab에서 마우스의 오른쪽 버튼을 클릭하여 “Create Hierarchy”를 수행하고 블록 이름을 uart0라고 지정한다. 그러면 조그만 box가 보이게 되는데 이 box를 확대하고 UART블럭을 드래그하여 box 안으로 넣는다. 그리고 DMA 블록과 DMA FIFO블럭을 모두 box안으로 넣는다. 그러면 다이어그램은 다음과 같이 변경된다.
위의 그림에서 uart0 블록을 더블클릭하면 uart0블럭의 다이어그램이 다음과 같이 나타난다.
이제 uartdma 블록이 10MHz의 클럭과 115200Hz의 클럭을 선택하여 동작하도록 변경하여 보자.
uartdma블록에 커서를 놓고 오른쪽 버튼을 눌러 Edit in IP Packager를 수행한다. 설계 도구가 실행되면 top design을 열고 IO 포트를 다음과 같이 변경한다.
clk10m : in std_logic;
clk115 : in std_logic;
uclko : out std_logic;
uarttx : out std_logic;
uartrx : in std_logic;
fsts : in std_logic_vector(1 downto 0);
signal 선언부에 다음을 추가한다.
signal uartclk : std_logic;
user logic 부분에 다음을 추가한다.
uartclk <= clk10m when(control(4) = '1') else clk115;
uclko <= uartclk;
즉 reg0의 4번째 비트에 1이 써지면 baud rate가 10M이고 0이면 115200이다. 저장하고 IP를 재생성한다. 클럭 신호가 변경되었기 때문에 uart0에서 clk100m 핀을클릭하고 마우스 오른쪽 버튼을 클릭하여 Create pin을 수행한다. clk115에 대해서도 동일하게 수행한다. 그리고 uclko를 DMA FIFO의 송신측에는 s_axis_aclk에 수신측에는 m_axis_aclk에 연결한다.
top design의 다이어그램에서 CPU에서 출력되는 fclk_clk1을 uart0의 clk10m에 연결한다.
이제 115200Hz의 클럭을 만들어서 연결해 주어야 하는데 불행하게도 CPU 클럭에서는 115200Hz의 클럭을 생성할 수가 없다. 그래서 xilinx에서 제공하는 clk wizard를 써서 115200Hz의 배수인 144MHz의 클럭을 만들고 분주회로를 IP로 만들어서 115200Hz의 클럭을 생성한다.
144MHz로부터 115200Hz의 클럭을 생성하기 위한 VHDL Code는 다음과 같다.
entity baud115 is
Port (
rstb : in std_logic;
c144m : in std_logic;
baud115o : out std_logic );
end baud115;
architecture Behavioral of baud115 is
signal dcnt : std_logic_vector(9 downto 0);
signal baud115d : std_logic;
begin
process(c144m)
begin
if(rising_edge(c144m)) then
if(rstb = '0') then
dcnt <= (others =>'0');
baud115d <= '0';
else
if(dcnt = "1001110000") then
dcnt <= (others => '0');
baud115d <= not baud115d;
else
dcnt <= dcnt + 1;
baud115d <= baud115d;
end if;
end if;
end if;
end process;
baud115o <= baud115d;
end Behavioral;
그리고, 일단 이전 프로그램에서 uart 블록의 클럭을 선택하는 레지스터를 115200 Hz와 10MHz의 클럭으로 동작하도록 설정하고 기능 확인을 한다. 다음 그림은 115200Hz로 전송하는 파형을 xilinx 내부 Logic Analyzer로 본 파형이다. 내부 로직 아날라이저의 기준 클럭을 100MHz로 하였기 때문에 한 클럭 시간이 10nsec이고, 아래 그림에서 보면 데이터 1비트를
표현하는 기간이 868 클럭임을 확인할 수 있고, 시간으로 보면 8780 nsec이므로 이를 역수로취해보면 115,207Hz 임을 알 수 있다.
다음 그림은 10MHz로 전송하는 파형을 xilinx 내부 Logic Analyzer로 본 파형이다. 1비트의 데이터를 나나태는 구간이 10 클럭이므로 10M Baud Rate임을 알 수 있다.
이제 uart0블럭을 복사하여 4개를 더 삽입한다. 그리고 나서 IO 핀들을 연결한다. 이제 인터럽트 신호에 대해 살펴본다.
원래는 uart가 한 채널일 때는 송수신 인터럽트 각 1개씩이어서 2개의 인터럽트 신호를 처리하면 되었으나 uart가 5 채널로 늘어나서 10개의 인터럽트 소스가 생겼으며 인터럽트 처리 루틴을 하나로 구성하기 위해 인터럽트 신호를 하나로 묶어서 CPU로는 하나의 인터럽트 신호만 보내고, 인터럽트 플래그를 CPU에서 볼수 있고, 해당 인터럽트를 Enable/Disable 시킬 수 있는 레지스터와 함께 만들어서 CPU에서 인터럽트를 처리할 수 있도록 인터럽트 신호를 취합하여 AXI 버스에 묶어 줄 수 있는 IP를 추가하여 연결한다. 최종 블록도는 다음 그림과 같다. 먼저 합성을 수행하고 합성 결과를 확인한 뒤 추가된 핀들을 할당하고 비트스트림을 생성한다.
3.3 시험
시험을 위한 SW는 이전의 SW에서 AXI DMA Device를 5개로 설정하고 그에 따른 초기화를 진행하고, uart블럭 5개의 초기화를 수행한뒤 RxIntrHandler와 TxIntrHandler을 하나의 인터럽트 핸들러로 통합하여 시험을 진행하였다.
uart block을 초기화 하기 위한 code는 다음과 같다.
void initRegAddr(void)
{
INTRADDR = (u32 *)XPAR_INTCNTR_0_BASEADDR;
UARTADDR[0] = (u32 *)XPAR_UART0_AXI_UARTDMA_0_S00_AXI_BASEADDR;
UARTADDR[1] = (u32 *)XPAR_UART1_AXI_UARTDMA_0_S00_AXI_BASEADDR;
UARTADDR[2] = (u32 *)XPAR_UART2_AXI_UARTDMA_0_S00_AXI_BASEADDR;
UARTADDR[3] = (u32 *)XPAR_UART3_AXI_UARTDMA_0_S00_AXI_BASEADDR;
UARTADDR[4] = (u32 *)XPAR_UART4_AXI_UARTDMA_0_S00_AXI_BASEADDR;
UARTADDR[0][0] = 0x01;
UARTADDR[1][0] = 0x01;
UARTADDR[2][0] = 0x01;
UARTADDR[3][0] = 0x01;
UARTADDR[4][0] = 0x01;
}
DMA를 초기화 하기 위한 Code는 다음과 같다.
int initDmaDev(void)
{
int i, Status, DMA_DEV_ID;
XAxiDma_Config *Config;
for(i=0;i<NUMBER_OF_UART;i++){
switch(i){
case 0: DMA_DEV_ID = DMA_DEV_ID0;break;
case 1: DMA_DEV_ID = DMA_DEV_ID1;break;
case 2: DMA_DEV_ID = DMA_DEV_ID2;break;
case 3: DMA_DEV_ID = DMA_DEV_ID3;break;
case 4: DMA_DEV_ID = DMA_DEV_ID4;break;
}
Config = XAxiDma_LookupConfig(DMA_DEV_ID);
if (!Config) {
xil_printf("No config found for %d\r\n", DMA_DEV_ID);
return XST_FAILURE;
}
/* Initialize DMA engine */
Status = XAxiDma_CfgInitialize(&AxiDma[i], Config);
if (Status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", Status);
return XST_FAILURE;
}
if(XAxiDma_HasSg(&AxiDma[i])){
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
}
return XST_SUCCESS;
}
인터럽트 핸들러의 Code는 다음과 같다.
static void IntrHandler(void *Callback)
{
u32 IrqStatus;
u32 IrqReg, ID;
int TimeOut, Status, i;
u8 *RxBufferPtr;
XAxiDma *AxiDmaInst;
RxBufferPtr = (u8 *)RX_BUFFER_BASE;
IrqReg = INTRADDR[0];
INTRADDR[1] = INTRADDR[1] & ~IrqReg;
if(IrqReg & 0x003f0000){
if(IrqReg&0x00010000) ID = 0;
else if(IrqReg&0x00020000) ID = 1;
else if(IrqReg&0x00040000) ID = 2;
else if(IrqReg&0x00080000) ID = 3;
else if(IrqReg&0x00100000) ID = 4;
else ID = 5;
/* Read pending interrupts */
IrqStatus = XAxiDma_IntrGetIrq(&AxiDma[ID], XAXIDMA_DMA_TO_DEVICE);
/* Acknowledge pending interrupts */
XAxiDma_IntrAckIrq(&AxiDma[ID], IrqStatus, XAXIDMA_DMA_TO_DEVICE);
/* * If no interrupt is asserted, we do not do anything */
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}
/*
* If error interrupt is asserted, raise error flag, reset the
* hardware to recover from the error, and return with no further
* processing.
*/
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1;
/* * Reset should never fail for transmit channel */
XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if (XAxiDma_ResetIsDone(&AxiDma[ID])) {
break;
}
TimeOut -= 1;
}
return;
}
/*
* If Completion interrupt is asserted, then set the TxDone flag
*/
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
TxDone = 1;
}
}
if(IrqReg & 0x0000003F){
if(IrqReg&0x00000001) ID = 0;
else if(IrqReg&0x00000002) ID = 1;
else if(IrqReg&0x00000004) ID = 2;
else if(IrqReg&0x00000008) ID = 3;
else if(IrqReg&0x00000010) ID = 4;
else ID = 5;
/* Read pending interrupts */
IrqStatus = XAxiDma_IntrGetIrq(&AxiDma[ID], XAXIDMA_DEVICE_TO_DMA);
/* Acknowledge pending interrupts */
XAxiDma_IntrAckIrq(&AxiDma[ID], IrqStatus, XAXIDMA_DEVICE_TO_DMA);
/* * If no interrupt is asserted, we do not do anything */
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}
/*
* If error interrupt is asserted, raise error flag, reset the
* hardware to recover from the error, and return with no further
* processing.
*/
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1;
/* * Reset should never fail for transmit channel */
XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if (XAxiDma_ResetIsDone(&AxiDma[ID])) {
break;
}
TimeOut -= 1;
}
return;
}
/*
* If Completion interrupt is asserted, then set the TxDone flag
*/
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
Status = XAxiDma_SimpleTransfer(&AxiDma[ID],(UINTPTR) RxBufferPtr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
RX_PKT_LEN = UARTADDR[ID][6];
RxDone = 1;
}
}
INTRADDR[1] = 0xffffffff;
}
다음은 1~400 바이트의 데이터를 uart0부터 uart4까지 순차적으로 전송한 자체 Loopback 시험 결과이다.
댓글
댓글 쓰기