學習內容
本文主要介紹關于ZYNQ芯片的串口中斷功能,并編寫相關讀寫測試代碼,完成串口中斷的讀寫測試。
開發環境
vivado 18.3&SDK,PYNQ-Z2開發板。
UART控制器
簡介
UART控制器是一個全雙工異步接收和發送,支持可編程波特率和I/O信號格式。 該控制器可實現奇偶校驗自動生成和多主檢測模式。UART操作由配置和模式寄存器控制。
FIFO、調制解調器信號和其他控制器功能的狀態是使用狀態、中斷狀態和調制解調器狀態寄存器讀取的。UART控制器有獨立的RX和TX數據路徑。每個路徑包括一個64字節的FIFO。 控制器TX和RX FIFO中的數據進行串行化和反串行化,并包括一個模式開關,以支持RXD和TXD信號的各種環回配置。(RXD和TXD使用模式:正常模式、各種環回診斷測試模式)
FIFO中斷狀態位支持輪詢或中斷驅動的處理程序。 軟件使用RX和TX數據端口寄存器讀取和寫入數據字節。當在類似于調制解調器的應用程序中使用UART時,調制解調器控制模塊檢測并生成調制解調器握手信號,并且還根據握手協議控制接收和發送器路徑。(調制解調器控制信號:CTS, RTS, DSR, DTR, RI和DCD只有在EMIO接口可用)
系統框圖
UART控制器的系統框圖如下圖所示:
在圖中,SLCR寄存器(系統級控制寄存器(SLCR)由用于控制PS行為的各種寄存器組成。這些寄存器可以通過中央互連使用加載和存儲指令訪問。)包括控制位用于UART時鐘,復位和MIO-EMIO信號映射。軟件可以使用APB 32位的從接口訪問UART控制器寄存器。每個控制器的IRQ(中斷號為59,82)連接到PS中斷控制器,并連接到PL。UART控制器由參考時鐘( UART REF_CLK)驅動,同時控制器也需要連接APB 總線時鐘( CPU_1x clock), UART REF_CLK 和 CPU_1x clock 都是來自于 PS 時鐘子系統。
內部框圖
UART控制器內部框圖如下圖所示。
由上圖可知,UART控制器使用PS AXI interconnect 進行數據交互,通過APB Slave接口來接收PS端口的配置信息和一些數據信息。假設要發送數據,則系統先將發生的字符串緩存到TxFIFO下,然后經Transmitter模塊實現并轉串,如果工作在正常模式下,則數據之間接到MIO/EMIO引腳上,正常向接收設備發送;假設要接收其他設備傳來的串口信息,則首先通過串口接收引腳接收串行數據,然后經過receiver模塊實現串轉并,轉換過后存入到RxFIFO下,經過APB從接口傳輸到PS端,即可對接收數據進行處理。
通過控制狀態寄存器可以對UART控制器進行控制,而這些引腳只能連接到EMIO。UART控制器內部包括一個中斷模塊,所以可以和其他模塊一樣正常接收到來自系統的中斷信號。對于UART的參考時鐘在控制器內部首先進行了一個八分頻,接著再產生波特率時鐘。
Transmit FIFO
Transmit FIFO (Tx FIFO)存儲由APB從接口寫入的數據,發送模塊收到FIFO中的數據后刪除FIFO的數據然后進行串并轉換,并裝入其移位寄存器。TxFIFO的最大數據寬度為8位。數據通過寫入TxFIFO寄存器加載到TxFIFO。
當數據加載到TxFIFO時,TxFIFO空標志將被清除并保持在這個Low狀態,直到TxFIFO中的最后一個字符被刪除并加載到發送器移位寄存器中。TxFIFO滿中斷狀態(TFULL)表明TxFIFO已經完全寫滿了,并且阻止數據被寫入到TxFIFO中。如果對TxFIFO執行另一個APB寫入操作,則觸發溢出,寫入數據不會加載到TxFIFO中。
發送 FIFO接近滿標志(TNFULL)表明在FIFO中沒有足夠的空間來進行一次程序大小的寫入,這是由模式寄存器的WSIZE位控制的。TxFIFO接近滿標志(TNFULL)表示TxFIFO中只有字節空閑。
可以在TxFIFO填充級別上設置一個閾值觸發器(TTRIG)。發射器觸發寄存器可以用來設置這個值,這樣當TxFIFO填充深度達到設定的閾值時觸發可以設置。
Receiver FIFO
Receiver FIFO和Transmit FIFO類似。RxFIFO存儲由接收器串行移位寄存器接收的數據。RxFIFO的最大數據寬度是8位。當數據加載到RxFIFO時,RxFIFO空標志被清除,并且這種狀態保持為低,直到RxFIFO中的所有數據通過APB接口傳輸完畢。空標志重新置位為高,如果接著讀FIFO的話,則會從空的RxFIFO讀取返回0。
RxFIFO滿狀態(Chnl_int_sts_reg0 [RFUL]和Channel_sts_reg0 [RFUL]位)表明RxFIFO滿了,阻止數據被加載到RxFIFO。同時也可以在RxFIFO上設置一個閾值觸發器(RTRIG)。接收器觸發級別寄存器(Rcvr_FIFO_trigger_level0)可以用來設置這個值,取值范圍是1 ~ 63。
I / O模式切換
這里的模式切換即為內部框圖中的 Mode Switch 模塊,如下圖所示。
該模式由mode_reg0 [CHMODE]寄存器設置控制,總共分為四種模式,分別 為:正常模式( Normal Mode)、自動回音模式( Automatic Echo Mode)、本地環回模式( Local Loopback Mode)和遠程環回模式( Remote Loopback Mode)。
正常模式( Normal Mode) :用于標準UART操作,就是發送接收功能。
自動回音模式( Automatic Echo Mode) :Automatic Echo Mode模式在RxD上接收數據,模式開關將數據連接到接收端和UARTx_TxD。而PS的TXD端口的數據無法正常發出。
本地環回模式( Local Loopback Mode) :本地環回模式不連接到RxD或TxD引腳,而是直接把PS發送的數據傳輸到PS的接收端。
遠程環回模式( Remote Loopback Mode) :遠程環回模式將RxD信號連接到TxD信號。在這種模式下,控制器不能在TxD上發送任何內容,也不能在RxD上接收任何內容。
UART啟動順序
UART 的啟動順序如下:
- 復位UART控制器,在PS進行系統復位時進行控制器的復位。
- 配置 IO 引腳信號。
- 配置 UART 參考時鐘(可以保護默認)。
- 配置控制器功能( UART 控制器初始化)。
- 配置中斷,通過中斷來管理 RxFIFO 和 TxFIFO。
- 配置串口模式控制(可選)。
- 管理發送和接收的數據,采用輪詢或中斷驅動處理兩種方式。
配置控制器功能步驟
在UART控制器中,控制器可以配置字符幀、波特率、FIFO觸發級別、Rx超時機制,并使能控制器。所有步驟都必須在復位之后。 配置控制器的功能步驟如下:
- 配置 UART 數據幀格式。 數據位長度、停止位、校驗方式、 IO 模式等。
- 設置波特率。
- 設置 RxFIFO 觸發器等級,可以選擇啟用或禁用該功能。
- 使能 UART 控制器。
- 配置接收器的超時機制,可以選擇啟用或禁用該功能。
發送數據步驟
編寫軟件程序時,可以通過使用輪訓和中斷的方式控制RxFIFO和TxFIFO中的數據流。
使用輪詢方式發送數據順序
- 檢查TxFIFO是否為空。直到uart.Channel sts rego[TEMPTY] =1,執行后續步驟。
- 用數據填充TxFIFO。向uart.TX_RX_FIFO0寄存器寫入64字節的數據。
- 向TxFIFO寫入數據。有兩種方法:方法一: 可以等待 TxFIFO 為空之后再寫入 64 個字節,即執行第 2 步;方法一: 可以檢測 TxFIFO 是否寫滿,即不停的讀取 TFUL 標志和寫單個字節的數據。
使用中斷方法發送數據的順序
- 禁用 TxFIFO 空狀態中斷。
- 寫入數據到TxFIFO中。
- 檢測TxFIFO是否還有足夠的空間容納數據。
- 重復2和3操作。
- 使能中斷。
- 等待TxFIFO為空,然后從步驟 1 重新開始。
接收數據步驟
使用輪詢方式發送數據順序
- 等待RxFIFO被填滿到觸發器級別。
- 從RxFIFO讀取數據。
- 重復步驟2,直到FIFO為空。
- 設置Rx超時中斷狀態位時清除。
使用中斷方法發送數據順序
- 使能中斷。
- 等待RxFIFO被填滿到觸發級別或Rx超時。
- 從RxFIFO讀取數據。
- 重復步驟2和3,直到FIFO為空。
- 如果設置了中斷狀態位,則清除中斷狀態位。
系統框圖
這里僅僅使用了UART部分,所以可以利用前文的helloworld工程,不需要進行特殊的配置。通過串口的中斷功能把發送的數據再接收到PS端進行一個回環顯示。
硬件平臺搭建
新建工程,創建 block design。添加ZYNQ7 ip,根據本次工程需要對IP進行配置。勾選本次工程使用的uart資源
將ZYNQ無用資源進行取消勾選:
硬件系統構建完成如下:
然后我們進行generate output product 然后生成HDL封裝。這里只用到了UART,是MIO引腳,所以不需要進行管腳分配。點擊導出硬件資源(可以不包含bit流文件,因為只用到了PS資源),接著launch SDK。
SDK軟件部分
打開SDK后,新建application project。 在system.mss中可以打開相關參考文檔輔助設計。
這里使用串口中斷可以參考賽靈思提供的例程代碼進行修改設計:
這里導入uart_intr_example例程模板在main.c中輸入以下代碼:
#include "xparameters.h"
#include "stdio.h"
#include "xuartps.h"
#include "xuartps_hw.h"
#include "xscugic.h"
#define UART_0_DEVICE_ID XPAR_PS7_UART_0_DEVICE_ID
#define INTR_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define UART_INT_IRQ_ID XPAR_XUARTPS_0_INTR
XUartPs Uart_Inst;
XScuGic ScuGic_Inst;
int uart_init();
void intr_init(XScuGic *intr, XUartPs *uart);
void UartIntr_Handler(void *call_back_ref);
int main(){
//uart初試化函數
uart_init();
xil_printf("intr\n");
//中斷初始化
intr_init(&ScuGic_Inst,&Uart_Inst);
while(1);
return0;
}
//uart初始化
int uart_init(){
XUartPs_Config *UartPs_Cfg;
int Status;
//查找配置信息
UartPs_Cfg= XUartPs_LookupConfig(UART_0_DEVICE_ID);
//對uart控制器進行初始化
XUartPs_CfgInitialize(&Uart_Inst, UartPs_Cfg, UartPs_Cfg->BaseAddress);
//檢測硬件搭建是否正確
Status = XUartPs_SelfTest(&Uart_Inst);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
//設置波特率
XUartPs_SetBaudRate(&Uart_Inst,115200);
//設置RXFIFO觸發閾值
XUartPs_SetFifoThreshold(&Uart_Inst,1);
//設置操作模式
XUartPs_SetOperMode(&Uart_Inst, XUARTPS_OPER_MODE_NORMAL);
return XST_SUCCESS;
}
//中斷初始化
void intr_init(XScuGic *intr, XUartPs *uart){
XScuGic_Config *IntcConfig;
//中斷控制器初始化
IntcConfig = XScuGic_LookupConfig(INTR_DEVICE_ID);
XScuGic_CfgInitialize(intr,IntcConfig,IntcConfig->CpuBaseAddress);
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler) XScuGic_InterruptHandler,
(void *)intr);
Xil_ExceptionEnable();
//為中斷設置中斷處理函數
XScuGic_Connect(intr, UART_INT_IRQ_ID,
(Xil_ExceptionHandler) UartIntr_Handler,
(void *) uart);
//設置觸發類型
XUartPs_SetInterruptMask(uart, XUARTPS_IXR_RXOVR);
//使能中斷
XScuGic_Enable(intr, UART_INT_IRQ_ID);
}
//中斷處理函數
void UartIntr_Handler(void *call_back_ref){
XUartPs *uartinst =(XUartPs *)call_back_ref;
u32 read_data = 0;
u32 intr_status;
//讀取中斷ID寄存器
intr_status = XUartPs_ReadReg(uartinst->Config.BaseAddress,
XUARTPS_IMR_OFFSET);//讀取掩碼
intr_status &= XUartPs_ReadReg(uartinst->Config.BaseAddress,
XUARTPS_ISR_OFFSET);//讀取狀態
if(intr_status & (u32)XUARTPS_IXR_RXOVR){
read_data = XUartPs_RecvByte(XPAR_PS7_UART_0_BASEADDR);//接收發送的字節
XUartPs_WriteReg(uartinst->Config.BaseAddress,XUARTPS_ISR_OFFSET,
XUARTPS_IXR_RXOVR);//清除中斷狀態
}
//設置發送
XUartPs_SendByte(XPAR_PS7_UART_0_BASEADDR,read_data);
}
部分代碼講解
在整體的代碼設計中,代碼思路如下:
- 初始化UART控制器
- 初始化UART中斷
- 編寫中斷服務函數
對于初始化UART部分,在#include "xuartps.h"
頭文件中可以找到很多配置的函數,調用即可對uart的波特率,中斷觸發閾值等參數進行設置。 對于中斷的配置可以類比GPIO的中斷配置函數,先在初始化SGIC,然后進行異常初始化,完成注冊異常并使能,接著需要連接SGIC和UART,最后設置中斷類型并使能完成中斷整體操作配置。 在中斷服務函數中,實現的功能為回環讀寫,所以要在中斷函數中進行檢測中斷類型,當進入相應的中斷時進行數據的讀取,讀取到上位機的發送數據,然后清除中斷標志,最后把讀到的數據進行發送。
Reference
- 正點原子視頻教程
- UG585