官术网_书友最值得收藏!

1.3 STM32寄存器簡介

STM32系統的開發方法通常有兩種,即寄存器開發和庫函數開發,其中寄存器開發是基礎,庫函數開發是在寄存器開發的基礎上發展而來的一種易于學習和編程的開發方法。雖然庫函數開發方法容易學習,編程簡單迅速,但是它畢竟是在寄存器開發的基礎上發展而來的,因此想要掌握庫函數開發,必須先對STM32寄存器的配置有一個基本的認識和了解。下面將主要以STM32F103ZET6芯片為例,介紹STM32寄存器。

1.3.1 STM32芯片的結構

常見的STM32芯片是已經封裝好的成品,能夠看到的只有芯片的外觀和芯片四周伸出的引腳,圖1.3展示了STM32F103ZET6芯片的外觀與引腳分布。該芯片為144引腳(LQFP144)封裝,芯片上的小圓點位置表示引腳1位置,從引腳l位置開始,所有引腳按逆時針順序排列。STM32F103ZET6芯片包括7個16位的通用輸入輸出(GPIO)端口,依次稱為PA、PB、PC、PD、PE、PF和PG。在芯片的設計過程中,為不同的引腳定義了不同的功能,并且幾乎每個GPIO端口都復用了其他功能(PG8和PG15例外),STM32F103ZET6芯片的引腳功能定義見附錄A。

圖1.3 STM32F103ZET6芯片的外觀與引腳分布

僅從芯片外部是無法捕捉到芯片的內部結構的,那么STM32芯片內部究竟包含著什么呢?其實它與常見的計算機主機很相似,計算機主機主要由CPU和主板、顯卡、內存、硬盤等外設組成,類似地,STM32芯片也是由內核和片上外設組成的。例如,STM32F103系列芯片的CPU即Cortex-M3內核,除了內核,還設有GPIO、USART(串口)、ADC、I2C、SPI等模塊,這些即片上外設。內核與片上外設之間通過各種總線連接,形成一個相互協調的統一整體。STM32F103系列芯片內部系統結構框圖如圖1.4所示。

圖1.4 STM32F103系列芯片內部系統結構框圖

STM32芯片主體系統由驅動單元和被動單元構成,驅動單元主要包括內核D-Code總線、System總線、通用DMA(Direct Memory Access,直接內存存取)總線等;被動單元主要包括AHB到APB的連接橋(用于連接所有的APB設備)、內部FLASH存儲器、內部SRAM和FSMC等。其中,各種總線是指令和數據的傳輸通道,也是連接內核與各種片上外設的紐帶,主要包括I-Code總線、D-Code總線、System總線、DMA總線、AHB總線和APB總線。下面具體講解一下圖1.4中的這幾條總線。

1. I-Code總線

I-Code總線連接內核與內部FLASH存儲器的指令接口,可實現指令的預取功能,是基于AHB-Lite總線協議的32位總線,讀取指令以字的長度執行。開發時,編寫好的程序經過編譯之后會變成單片機能夠識別的一條條指令,而這些指令一般會被存放在內部FLASH存儲器中,內核想要讀取這些指令從而執行程序就必須通過I-Code總線來完成。

2. D-Code總線

D-Code總線連接內核與內部FLASH存儲器的數據接口,可實現數據訪問功能,也是基于AHBLite總線協議的32位總線。在編程開發時,用到的數據有常量和變量兩種,在存儲過程中,常量會被放到內部FLASH存儲器中,而全局變量和局部變量會被存放到內部SRAM中。當在執行指令的過程中,內核需要訪問內部FLASH存儲器中存放的數據時,必須通過D-Code總線來讀取。

3. System總線

System總線即系統總線,連接內核和總線矩陣。System總線通過總線矩陣可以訪問外設寄存器,通常說的寄存器開發,即編程讀/寫寄存器,就是通過System總線來完成的。

4. DMA總線

DMA總線實現DMA的AHB主控接口到總線接口的連接,主要用來訪問和傳輸數據,這些數據可以是某個外設的數據寄存器中的,可以是SRAM中的,也可以是內部FLASH存儲器中的。因為內部FLASH存儲器中的數據既可以通過D-Code總線訪問,也可以通過DMA總線訪問,為了避免訪問沖突,在讀取數據時需要通過總線矩陣來進行仲裁,最終決定通過哪條總線來讀取數據。總線矩陣的作用正是仲裁協調內核和DMA之間的訪問,此仲裁利用輪換算法。

5. AHB總線和APB總線

AHB(Advanced High Performance Bus)和APB(Advanced Peripheral Bus)是ARM公司推出的AMBA片上總線規范的主要總線結構。

AHB是Advanced High Performance Bus的縮寫,可譯為高級高性能總線,它通過總線矩陣與System總線相連,允許DMA訪問,主要用于高性能模塊(如CPU、DMA和DSP等)之間的連接。AHB系統由主模塊、從模塊和基礎結構三部分組成,整個AHB總線上的傳輸由主模塊發出,由從模塊負責回應,基礎結構則由仲裁器(Arbiter)、主模塊到從模塊的多路器、從模塊到主模塊的多路器、譯碼器(Decoder)、虛擬從模塊(Dummy Slave)、虛擬主模塊(Dummy Master)組成。

APB是Advanced Peripheral Bus的縮寫,是一種外圍總線,它主要用于低帶寬的周邊外設之間的連接,如UART、1284等。它的總線架構不像AHB支持多個主模塊,在APB中唯一的主模塊就是APB橋,再往下,由于不同的外設需要的時鐘(高速時鐘和低速時鐘)不同,APB總線分為低速外設總線APB1和高速外設總線APB2,其上分別掛載著不同的外設。APB1操作速率限于36MHz,APB2操作于全速(最高為72MHz);APB1負責DA、USB、SPI、I2C、CAN、USART2~5和普通TIMx,APB2負責AD、I/O、USART1和高級TIMx。

這些總線通過相互協調,將芯片的內核與內部FLASH存儲器、SRAM、FSMC和各種片上外設連接起來,形成一個相互配合、統一調配的整體,從而構成了強大的STM32芯片。

1.3.2 從存儲區映射到寄存器

1. 存儲區映射與寄存器映射

盡管擁有多條總線,STM32芯片內部的存儲區仍然是一個大小為4GB的線性地址空間。FLASH、SRAM、FSMC和各種片上外設等部件通過存儲區的地址分配,共同排列在這個4GB的地址空間中。在編程開發時,通過在存儲區中的位置地址找到它們,進而通過指令操作它們。給存儲區分配地址的過程稱為存儲區映射,如果給存儲區再分配一個地址就叫存儲區重映射。

對于這個4GB的地址空間,ARM公司已經將它平均劃分成了8塊,每塊512MB,分別規定了不同的用途。STM32存儲區映射方案如圖1.5所示。

圖1.5 STM32存儲區映射方案

在上述8塊中,Block0被劃分為代碼區,主要用于裝載和執行指令代碼,其起始地址為0x00000000。在代碼區中,0x00000000~0x0007FFFF的地址范圍為FLASH、系統存儲器和SRAM的別名區;對于STM32F103ZET6單片機而言,0x08000000~0x0807FFFF的地址范圍劃分為內部FLASH存儲器,共為512KB,屬于大容量片上閃存,通過I-Code總線與內核連接,主要用于裝載和存儲指令代碼;0x1FFFF000~0x1FFFF7FF的地址范圍劃分為系統存儲器,存儲著芯片出廠時即燒寫好的isp自舉程序(Bootloader),用戶無法改動,這部分程序會在串口下載時用到;0x1FFFF800~0x1FFFF80F的地址范圍用于配置讀/寫保護、BOR級別、軟硬件看門狗和部件處于待機或停止模式下的復位,當芯片不小心鎖住之后,可以從RAM里啟動,以修改這部分相應的位;其余地址范圍為預留空間。

Block1被劃分為SRAM區,其起始地址為0x20000000,所有的內部SRAM都位于底部的位帶區。SRAM主要用于存放各類局部變量和全局變量,也可以用來裝載和執行指令代碼,但是這樣做會使得內核不得不通過System總線來讀取指令,從而會產生額外的CPU等待周期,因此在SRAM中裝載和執行指令代碼要比在FLASH中緩慢。

Block2被劃分為片上外設區,其起始地址為0x40000000,所有片上外設的存儲映射地址必須位于外設位帶區。其中,APB1總線外設的地址范圍為0x40000000~0x400077FF,APB2總線外設的地址范圍為0x40010000~0x40013FFF,AHB總線外設的地址范圍為0x40018000~0x5003FFFF。在這些區域中,每4個字節共32位作為一個單元,每個單元對應不同的功能,控制這些單元就能夠驅動外設進行相應的工作。先找到每個單元的起始地址(具體如何找,后面會進行詳細的講解),當找到所需單元的起始地址之后,就可以通過C語言的指針操作方法來訪問這些單元,從而驅動外設進行相應的工作。但是如果每次都通過這種方式訪問,不僅費時費力,還容易出錯,為了解決這個問題,可以根據每個單元功能的不同,以功能為名給這個內存單元取一個別名,這個別名就是寄存器,這個給已經分配好地址、有特定功能的內存單元取別名的過程就叫作寄存器映射。

Block3~Block6共2GB的存儲區空間是用來拓展外部SRAM和外設的;Block7是Cortex-M3內核的內部外設區,其中內部私有設備的地址范圍為0xE0000000~0xE003FFFF;外部私有設備的地址范圍為0xE0040000~0xE00FFFFF;其余地址范圍為自由定制區,自由定制區的一部分是為生產商將來對Cortex-M3內核增加特殊功能而預留的。所有使用Cortex-M3內核的微控制器的內核寄存器都處于同一地址位置,這就使得所編寫的代碼在以Cortex-M3為內核的不同型號微控制器之間的移植變得更加容易。

2. STM32寄存器尋址與解讀

Block2作為片上外設區,其地址范圍主要依據APB1、APB2和AHB三條總線進行劃分,根據外設速度不同,不同總線掛載著不同的外設,APB1總線掛載低速外設,APB2總線和AHB總線掛載高速外設。相應總線的最低地址稱為該總線的基地址,在這三條總線中,APB1總線的地址最低,片上外設地址從這里開始,因此該總線基地址也稱為片上外設基地址。三條總線的基地址如表1.2所示,其中相對片上外設基地址的偏移是指該總線基地址與片上外設基地址0x40000000之間的差值。

表1.2 三條總線的基地址

三條總線上分別掛載著不同的外設,每個外設都有自己的地址范圍,特定外設的首個地址稱為該外設的基地址,也稱為該外設的起始地址。在該特定外設的地址范圍中,分布著該外設的寄存器,每個寄存器為32位,占4個字節,從該外設的基地址開始,按順序排列,寄存器的地址是以寄存器相對于其外設基地址的偏移地址來描述的。通過編程訪問這些寄存器,就可以驅動外設完成相應的工作。這里以GPIO端口為例,講解如何解讀外設的基地址及其寄存器,其他外設的基地址和寄存器的具體說明可以查閱《STM32F10x中文參考手冊》。GPIO端口是通用輸入輸出端口的簡稱,其基本功能是控制STM32引腳輸出高電平或低電平,屬于高速外設,掛載在APB2總線上。GPIO端口基地址如表1.3所示。

表1.3 GPIO端口基地址

STM32F103ZET6單片機的GPIO端口分為7組,即GPIOA~GPIOG,每組端口都分別有一套相同功能寄存器,即兩個32位配置寄存器(GPIOx_CRL和GPIOx_CRH)、兩個32位數據寄存器(GPIOx_IDR和GPIOx_ODR)、一個32位置位/復位寄存器(GPIOx_BSRR)、一個16位復位寄存器(GPIOx_BRR)和一個32位配置鎖定寄存器(GPIOx_LCKR)。這里以GPIOA端口為例,展示一下GPIO端口中的寄存器。GPIOA端口的寄存器地址列表如表1.4所示。

表1.4 GPIOA端口的寄存器地址列表

關于STM32外設寄存器的詳細說明可以查閱《STM32F10x中文參考手冊》,這里僅以端口置位/復位寄存器(GPIOx_BSRR)為例,介紹一下應該如何解讀寄存器的說明。端口置位/復位寄存器的詳細說明如圖1.6所示。

圖1.6 端口置位/復位寄存器的詳細說明

由圖1.6可以看出,寄存器說明首先給出了該寄存器的名稱,即“端口置位/復位寄存器(GPIOx_BSRR,x=A,B,···,G)”,其中,“x=A,B,···,G”表示該寄存器名稱中的“x”可以為A~G,說明這個寄存器適用于GPIOA~GPIOG 7組端口中的任意一組,即每組GPIO端口都有一個這樣的端口置位/復位寄存器。

緊跟著寄存器名稱的是該寄存器的地址偏移,是指該寄存器相對于其外設基地址的偏移,這個偏移量可以用來計算該寄存器的地址。前面也提到過,寄存器的地址是以寄存器相對于其外設基地址的偏移地址來描述的。本寄存器的偏移地址為0x10,從《STM32F10x中文參考手冊》中可以查到GPIOA端口外設的基地址為0x40010800,因此可以算出GPIOA端口置位/復位寄存器GPIOA_BSRR的地址為0x40010800+0x10=0x40010810。同理,根據GPIOB端口的外設基地址為0x40010C00,也可算出GPIOB_BSRR的地址為0x40010C10,其他GPIO端口寄存器地址的計算方法以此類推。

寄存器在復位之后每一位的值將轉變為初始態,這個初始態的數值由復位值決定,這里的復位值為0x00000000,表示該寄存器在復位之后,所有位都清零。復位值下面緊跟著的是該寄存器的位表,列出它的0~31位的名稱和讀寫權限:表上方的數字表示位編號,表中是對應位的名稱,表下方是對應位的讀寫權限,其中w表示只寫,r表示只讀,rw表示可讀可寫。該寄存器中的所有位權限都是w,所以只有寫的權限,如果要讀本寄存器,則無法保證能夠讀取到它的真正內容。當然也有一些寄存器的位權限為r,這些寄存器一般用于表示STM32外設的某種工作狀態,由STM32硬件自動更改,這樣就可以通過讀取這些寄存器位來判斷外設的工作狀態。

寄存器位表下面是對位功能的介紹,這是寄存器說明中最為重要的部分,它詳細說明了寄存器每一位的功能,按照功能介紹對相應的位進行操作,即可實現相應的功能。圖1.6中的位功能介紹表明該寄存器有兩種寄存器位,分別為BRy和BSy,其中y的值可以是0~15,這里的0~15表示端口的引腳號,如BR0、BS0用于控制GPIOx的第0個引腳,而BR1、BS1則用于控制GPIOx的第1個引腳。

根據位功能介紹中的描述,對BRy引腳的介紹是“0:對對應的ODRy位不產生影響。1:清除對應ODRy位為0”,對BSy引腳的介紹是“0:對對應的ODRy位不產生影響。1:設置對應ODRy位為1”。其中ODRy是指端口輸出數據寄存器(GPIOx_ODR)中相應的寄存器位,通過查閱《STM32F10x中文參考手冊》可以知道,當ODRy位為1時,對應的引腳y輸出高電平;當ODRy位為0時,對應的引腳y輸出低電平。這里對GPIOx_ODR寄存器不再詳述,讀者可以自行查閱《STM32F10x中文參考手冊》。通過位功能介紹可以知道,當對BR0寫入0時,不會影響對應的ODR0位,所以對應GPIOx的第0個引腳電平不會發生改變;當對BR0寫入1時,對應的第0個引腳會輸出低電平。同樣,當對BS0寫入0時,不會影響對應的ODR0位,對應的第0個引腳電平不會發生改變;當對BS0寫入1時,對應的第0個引腳會輸出高電平。由此可以看出,BRy和BSy這兩種寄存器位的功能效果是相反的。

前面的介紹已經說明了如何根據端口置位/復位寄存器說明來計算該寄存器的地址和如何解讀該寄存器的功能。實際上,雖然不同寄存器的地址和功能不相同,但是計算寄存器的地址和解讀寄存器功能的方法大致相同,讀者可以根據《STM32F10x中文參考手冊》自行解讀,這里不再詳述。

1.3.3 寄存器的封裝與讀/寫操作

1. 寄存器的封裝過程

前面已經講過,當找到所需要的配置寄存器的起始地址后,就可以通過C語言的指針操作方法來訪問相應的位置,進而根據寄存器的功能來驅動外設進行相應的工作。當然,面對STM32強大的功能,在進行復雜操作的過程中,反復尋找各種寄存器的地址和不斷進行各種指針操作仍然會浪費寶貴的開發時間。幸運的是,在STM32標準函數庫中,已經通過C語言對這部分煩瑣的工作進行了封裝,具體封裝過程如下。

(1)對總線和外設基地址進行封裝。

上述程序代碼首先定義了片上外設基地址PERIPH_BASE,并在此基礎上加入各個總線的地址偏移,從而得到APB1和APB2總線的基地址APB1PERIPH_BASE、APB2PERIPH_BASE;其次在APB2總線基地址上加入外設的地址偏移,得到GPIOA~CPIOG的外設基地址;最后在外設基地址上加入各寄存器的地址偏移,就得到了特定的寄存器基地址,通過指針讀/寫即可修改相應的寄存器位。

(2)對寄存器列表進行封裝。通過上面的方法定義地址還是略顯煩瑣,如GPIOA~GPIOG的每組端口都有一套功能相同的寄存器,它們只是地址不一樣,卻要為每個寄存器定義地址。為了更方便地訪問寄存器,在STM32標準函數庫中引入結構體的方法對寄存器進行封裝。

上述程序代碼用typedef關鍵字聲明了名為GPIO_TypeDef的結構體類型,結構體有7個成員變量,變量名正好對應寄存器的名字,其中32位變量占用4個字節,16位變量占用2個字節。由于結構體內變量的存儲空間是連續的,如果這個結構體的首地址為0x40010C00(也是第1個成員變量CRL的地址),那么結構體中第2個成員變量CRH的地址為0x40010C00+0x04,加上的這個0x04代表CRL所占用的4個字節地址的偏移量,其他成員變量的地址以此類推。這樣,成員變量之間的偏移就與GPIO外設定義的寄存器地址偏移一一對應,只要給結構體設置好首地址,就能把結構體內成員的地址確定下來,之后就能以結構體指針的形式訪問寄存器了。

為了使編程更加方便,直接使用宏將GPIO_TypeDef類型的指針定義好,每個指針指向各個GPIO端口的首地址,由此就可以直接用該宏訪問相應的寄存器了。

這里僅以GPIO這個外設為例,講解C語言對寄存器的封裝,其他外設寄存器的封裝過程與此類似,這部分工作已經由STM32標準函數庫完成,這里只需要了解即可。

2. 修改寄存器位與位帶操作

在了解了STM32寄存器的封裝過程之后,就能夠利用C語言對相應的寄存器賦值,并執行具體的操作。

(1)修改寄存器位的方法。在實際應用過程中,常常要求只修改該寄存器某幾位的值,而其他寄存器位不變,這個時候就需要用到C語言的位操作方法。

① 把變量的某位清零。此處以變量a代表寄存器,并假設寄存器中已有數值,此時需要把變量a的某一位清零,而其他位不變,具體操作方法如下。

在上述程序代碼中,括號中的1左移兩位可以得二進制數00000100b,按位取反后即可得到11111011b,所得的二進制數與a進行“與”運算,最終得到的a的值為10011011b,這樣就實現了a的bit2被清零,而其他位不變。

② 把變量的某幾個連續位清零。由于在寄存器中有時會有連續幾個寄存器位用于控制某個功能,現假設需要把寄存器的某幾個連續位清零,而其他位不變,具體操作方法如下。

在上述程序代碼中,括號中的3左移兩位可以得二進制數00001100b,按位取反后即可得到11110011b,所得的二進制數與a進行“與”運算,最終得到的a的值為10010011b,這樣,就實現了a的bit2和bit3被清零,而其他位不變;同理,括號中的7左移四位可以得二進制數01110000b,按位取反后即可得到10001111b,所得的二進制數與b進行“與”運算,最終得到的b的值為10000111b,這樣就實現了b的bit4、bit5和bit6被清零,而其他位不變。

③ 對變量的某幾位進行賦值。在實際應用中,有時還需要對寄存器中的某幾位進行賦值,而其他位不變,從而實現所需要的功能,具體操作方法如下。

在上述程序代碼中,括號中的5左移三位可以得二進制數00010100b,與a進行“或”運算,最終得到的a的值為10010111b,這樣就實現了a的bit2和bit4被賦值,而其他位不變。

④ 對變量的某位取反。在某些情況下,需要對寄存器的某位進行取反操作,而其他位不變,具體操作方法如下。

在上述程序代碼中,括號中的1左移6位可以得二進制數01000000b,與a進行“異或”運算,最終得到的a的值為11000011b,這樣就實現了a的bit6被取反,而其他位不變。

(2)位帶操作簡介。從修改寄存器位的方法可以看出,STM32不能像傳統51單片機那樣可以簡單地對端口中的某一位進行置位或復位操作,但是能夠通過“與”“或”等邏輯指令來實現寄存器或存儲區的位操作。通過邏輯運算進行的位操作實際上是一個“讀—修改—寫”的過程,在實現單個位操作的過程中會耗費數個時鐘周期,并且會增加代碼量。

為了克服這一限制,Cortex-M3內核引入了一種稱為位帶的操作技術,在不引入特殊指令的前提下,實現了SRAM區和片上外設區兩個區域的位操作。Cortex-M3內核的可位尋址區域由位帶區(SRAM起始的1MB空間和片上外設區起始的1MB空間)和兩個大小為32MB的位帶別名區組成,前面在介紹STM32存儲區映射時也曾涉及。位帶存儲映射如圖1.7所示。

圖1.7 位帶存儲映射

位帶技術將位帶區的每一位映射到對應的位帶別名區,只要對位帶別名區進行字操作就可以實現對真實內存的位操作,這將允許在不加入任何特殊指令的前提下實現位操作,同時避免了復雜的布爾運算,保持了Cortex-M3內核尺寸的小巧性。在實際應用中,當對一個SRAM或外設寄存器進行位操作時,需要計算與該位對應的位帶別名區中的地址,可使用以下的映射公式進行計算(其中位序號的取值范圍為0~7)。

① 位帶別名區地址=位帶別名區基地址+位帶別名區偏移地址。

② 位帶別名區偏移地址=(位帶區偏移地址×8+位序號)×4。

位帶區與位帶別名區的映射對應關系如圖1.8所示。STM32的系統總線是32位的,按照4字節進行訪問時的效率最高,因此位帶區上的每一位在位帶別名區上都會有4字節,即32位與之對應,這也是為什么每個位帶區1MB的空間映射到位帶別名區需要有32MB空間的原因。如圖1.8所示,0x22000000是SRAM位帶別名區的起始地址,假如需要求SRAM位帶區中0x20000000字節上bit7在位帶別名區中對應的地址,則根據映射公式需要先求得相應的位帶別名區偏移地址,0x20000000字節相對于位帶區基地址的偏移量為0x00,bit7的位序號為7,位帶別名區偏移地址為(0×32+7)×4=28,即0x1C,所以可以求出對應的位帶別名區地址為0x22000000+0x1C=0x2200001C。同樣,對于外設寄存器的位帶別名區地址的計算也是如此。

圖1.8 位帶區與位帶別名區的映射對應關系

計算出位在位帶別名區中所對應的地址后,就能夠通過對位帶別名區的字操作實現對位帶區的位操作。片上外設的位帶區覆蓋了全部的片上外設的寄存器,可以通過宏為寄存器的位定義一個位帶別名區地址,從而實現寄存器的位操作。這里僅對GPIOA中的端口輸入數據寄存器IDR和端口輸出數據寄存器ODR的位帶操作步驟進行演示,其他寄存器的位帶操作與之類似。

從《STM32F10x中文參考手冊》中可以查到IDR和ODR兩個寄存器對應GPIO基地址的偏移量分別為0x08和0x0C。

① 地址映射操作。

② 輸入輸出位操作。

這樣就能直接通過對PAin(n)或PAout(n)進行賦值實現GPIOA中某一I/O端口的輸入/輸出位操作,如在主函數中輸入“PAout(2)=0;”表示PA2端口輸出低電平;在主函數中輸入“PAout(5)=1;”表示PA5端口輸出高電平。

主站蜘蛛池模板: 翼城县| 五家渠市| 抚宁县| 怀安县| 德保县| 墨竹工卡县| 东乌珠穆沁旗| 阳城县| 建阳市| 小金县| 登封市| 上林县| 和顺县| 白水县| 富锦市| 泰兴市| 安仁县| 自治县| 通海县| 大姚县| 磐安县| 师宗县| 濮阳县| 扬州市| 衡阳市| 芜湖县| 额敏县| 佳木斯市| 石河子市| 武汉市| 旬邑县| 三穗县| 丹东市| 兴和县| 宁德市| 溧水县| 澜沧| 安阳县| 合作市| 博野县| 长岭县|