一、FSMC外擴內存SRAM
FSMC(Flexible Static Memory Controller,靈活的靜態存儲控制器)是STM32系列采用的一種新型的存儲器擴展技術,使用FSMC外設來管理擴展的存儲器。在外部存儲器擴展方面具有獨特的優勢,可根據系統的應用需要,方便地進行不同類型大容量靜態存儲器的擴展。FSMC可以用于驅動包括SRAM、NOR FLASH以及NAND FLSAH類型的存儲器,不能驅動如SDRAM這種動態的存儲器。而在STM32F429系列的控制器中,它具有FMC外設,支持控制SDRAM存儲器。
FSMC是STM32系列中內部集成256 KB以上FlaSh,后綴為xC、xD和xE的高存儲密度微控制器特有的存儲控制機制。之所以稱為“可變”,是由于通過對特殊功能寄存器的設置,FSMC能夠根據不同的外部存儲器類型,發出相應的數據/地址/控制信號類型以匹配信號的速度,從而使得STM32系列微控制器不僅能夠應用各種不同類型、不同速度的外部靜態存儲器,而且能夠在不增加外部器件的情況下同時擴展多種不同類型的靜態存儲器,滿足系統設計對存儲容量、產品體積以及成本的綜合要求。
- 常用存儲器介紹
存儲器的種類:存儲器是計算機結構的重要組成部分。存儲器是用來存儲程序代碼和數據的部件,有了存儲器計算機才具有記憶功能。
1.RAM存儲器:RAM是“Random Access Memory”的縮寫,被譯為隨機存儲器。所謂“隨機存取”,指的是當存儲器中的消息被讀取或寫入時,所需要的時間與這段信息所在的位置無關。這個詞的由來是因為早期計算機曾使用磁鼓作為存儲器,磁鼓是順序讀寫設備,而RAM可隨讀取其內部任意地址的數據,時間都是相同的,因此得名。
實際上現在RAM已經專門用于指代作為計算機內存的易失性半導體存儲器。根據RAM的存儲機制,又分為動態隨機存儲器DRAM(Dynamic RAM)以及靜態隨機存儲器SRAM(Static RAM)兩種。
2.DRAM存儲器:動態隨機存儲器 DRAM的存儲單元以電容的電荷來表示數據,有電荷代表 1,無電荷代表 0,,代表 1 的電容會放電,代表 0的電容會吸收電荷,因此它需要定期刷新操作,這就是“動態(Dynamic)”。
根據 DRAM的通訊方式,又分為同步和異步兩種,這兩種方式根據通訊時是否需要使用時鐘信號來區分。由于使用時鐘同步的通訊速度更快,所以同步 DRAM 使用更為廣泛,這種DRAM 被稱為 SDRAM(Synchronous DRAM)。常見的DRAM大多是SDRAM。
為了進一步提高 SDRAM的通訊速度,人們設計了DDR SDRAM (Double DataRate SDRAM)存儲器。DDR SDRAM在時鐘的上升沿及下降沿各表示一個數據,也就是說在 1 個時鐘周期內可以表示 2數據,在時鐘頻率同樣的情況下,提高了一倍的速度。
DDRII和 DDRIII,它們的通訊方式并沒有區別主要是通訊同步時鐘的頻率提高了。
3.SRAM存儲器:靜態隨機存儲器 SRAM的存儲單元以鎖存器來存儲數據,見圖。這種電路結構不需要定時刷新充電,就能保持狀態(當然,如果斷電了,數據還是會丟失的),所以這種存儲器被稱為“靜態(Static)”RAM。
同樣地,SRAM 根據其通訊方式也分為同步SRAM和異步 SRAM,相對來說,異步SRAM用得較多。
所以在實際應用場合中,SRAM一般只用于CPU內部的高速緩存(Cache),而外部擴展的內存一般使用DRAM。DRAM和SRAM的特性對比如下:
4.非易失性存儲器:非易失性存儲器種類非常多,半導體類的有 ROM 和 FLASH,而其它的則包括光盤、軟盤及機械硬盤。
ROM是“Read Only Memory”的縮寫,意為只能讀的存儲器。由于技術的發展,后來設計出了可以方便寫入數據的ROM,而這個“Read Only Memory”的名稱被沿用下來了,現在一般用于指代非易失性半導體存儲器,包括FLASH存儲器,有些人也把它歸到ROM類里邊。
FLASH存儲器又稱為閃存,它也是可重復擦寫的儲器,部分書籍會把FLASH存儲器稱為FLASH ROM,但它的容量一般比EEPROM大得多,且在擦除時,一般以多個字節為單位。根據存儲單元電路的不同,FLASH存儲器又分為NOR FLASH和NAND FLASH。
- SRAM控制原理
STM32控制器芯片內部有一定大小的SRAM及FLASH作為內存和程序存儲空間,但當程序較大,內存和程序空間不足時,就需要在STM32芯片的外部擴展存儲器了。STM32F407系列芯片可以擴展外部SRAM用作內存。
給STM32芯片擴展內存與給PC擴展內存的原理是一樣的,只是PC上一般以內存條的形式擴展,而且內存條實質是由多個內存顆粒(即SDRAM芯片)組成的通用標準模塊,而STM32擴展時,直接與SRAM芯片連接。
- SRAM外觀與內部框圖
①地址數據接口:A0-A17用作地址線,I/O0-I/O15用于傳輸數據,一次性傳輸16位數據,并行傳輸
②存儲矩陣:每個存儲單元16位,SRAM內部包含的存儲陣列,可以把它理解成一張表格,數據就填在這張表格上。和表格查找一樣,指定一個行地址和列地址,就可以精確地找到目標單元格而這樣的表則被稱為存儲矩陣。
當選中一個數據單元后,可以通過UB#或 LB#其中一個設置為低電平,I/O會對應輸出該地址的高 8位和低 8位數據,因此它們被稱為數據掩碼信號。
③控制電路:控制電路主要包含了片選、讀寫使能以及上面提到的寬度控制信號UB#和LB#。利用CE#片選信號,可以把多個SRAM芯片組成一個大容量的內存條。OE#和WE#可以控制讀寫使能,防止誤操作。整個數據的傳輸都是在控制電路的控制下完成的,當想要讀數據時,使用到OE線,把它拉低;想要寫數據時應當把WE線拉低;只想讀存儲單元的高8位,則拉低UB線;只想讀存儲單元的低8位,則拉低LB線;一次性讀26位都拉低即可。
SRAM的控制比較簡單,只要控制信號線使能了訪問,從地址線輸入要訪問的地址,即可從I/O數據線寫入或讀出數據。
- SRAM的讀時序
對什么時候使能讀信號、什么時候拉低片選、地址線號線維持的時間等都有一定的要求,重點時序包括:讀周期時間(tRC)、地址建立時間(tAA)、OE建立時間(tDOE),這些具體的時間都可在SRAM芯片的數據手冊上找到。
- SRAM的寫時序
重點時序:寫周期時間(tWC)、地址建立時間(tSA)、WE脈寬(tPWE)
- SRAM的讀寫流程
讀寫時序的流程很類似,過程如下:
(1) 主機使用地址信號線發出要訪問的存儲器目標地址;
(2) 控制片選信號CE#使能存儲器芯片;
(3) 若是要進行讀操作,則控制讀使能信號OE#表示要讀數據,若進行寫操作則控制寫使能信號WE#表示要寫數據;
(4) 使用掩碼信號LB#與UB#指示要訪問目標地址的高、低字節部分;
(5) 若是讀取過程,存儲器會通過數據線向主機輸出目標數據,若是寫入過程,主要使用數據線向存儲器傳輸目標數據。
- STM32-FSMC控制器框圖分析
①通訊引腳:由于控制不同類型存儲器的時候會有一些不同的引腳,看起來有非常多,其中地址線FSMC_A和數據線FSMC_D是所有控制器都共用的。
注:其中比較特殊的FSMC_NE是用于控制SRAM芯片的控制信號線,STM32具有FSMC_NE1/2/3/4號引腳,不同的引腳對應STM32內部不同的地址區域。
②存儲器控制器:上面不同類型的引腳是連接到FSMC內部對應的存儲控制器中NOR/PSRAM/SRAM設備使用相同的控制器,NAND/PC卡設備使用相同的控制器,不同的控制器有專用的寄存器用于配置其工作模式。
控制SRAM的有FSMC_ BCR 、FSMC_ BTR以及FSMC_BWTR寄存器。每種寄存器都有4個,分別對應于4個不同的存儲區域,各種寄存器介紹如下:
a.FSMC_BCR控制寄存器:可配置要控制的存儲器類型、數據線寬度以及信號有效極性能 參數。
b.FMC_BTR時序寄存器:用于配置SRAM訪問時的各種時間延遲,如數據保持時間、地址保持時間等。
c.FMC_BWTR寫時序寄存器:與FMC_BTR寄存器控制的參數類似,它專門用于控制寫時序的時間參數。
③時鐘控制邏輯:FSMC外設掛載在AHB總線上,時鐘信號來自于HCLK(默認168MHz),控制器的同步時鐘輸出就是由它分頻得到。
例如,NOR控制器的FSMC_CLK引腳輸出的時鐘,它可用于與同步類型的SRAM芯片進行同步通訊,它的時鐘頻率可通過FSMC_BTR寄存器的CLKDIV位配置,可以配置為HCLK的1/2或1/3,也就是說,若它與同步類型的SRAM通訊時,同步時鐘最高頻率為84MHz。
后面示例中的SRAM為異步類型的存儲器,不使用同步時鐘信號,所以時鐘分頻配置不起作用。
- FSMC地址映射
FSMC連接好外部的存儲器并初始化后,就可以直接通過訪問地址來讀寫數據。
FSMC訪問存儲器的方式與I2C EEPROM、SPI FLASH的不一樣,后兩種方式都需要控制I2C或SPI總線給存儲器發送地址,然后獲取數據;在程序里,這個地址和數據都需要分開使用不同的變量存儲,并且訪問時還需要使用代碼控制發送讀寫命令。
而使用FSMC外接存儲器時,其存儲單元是映射到STM32的內部尋址空間的;在程序里,定義一個指向這些地址的指針,然后就可以通過指針直接修改該存儲單元的內容,FSMC外設會自動完成數據訪問過程,讀寫命令之類的操作不需要程序控制。
FSMC的NOR/PSRAM/SRAM/NAND FLASH以及PC卡的地址被映射到了External RAM地址空間內,使得訪問FSMC控制的存儲器時,就跟訪問STM32的片上外設寄存器一樣。
FSMC把整個External RAM存儲區域分成了4個Bank區域,并分配了地址范圍及適用的存儲器類型,如NOR及SRAM存儲器只能使用Bank1的地址。
在NOR及SRAM區域,每個Bank的內部又分成了4個小塊,每個小塊有相應的控制引腳用于連接片選信號,如FSMC_NE[4:1]信號線可用于選擇BANK1內部的4小塊地址區域,當STM32訪問0x68000000-0x6BFFFFFF地址空間時,會訪問到Bank1的第3小塊區域,相應的FSMC_NE3信號線會輸出控制信號。
FSMC讀時序如下:
FSMC寫時序如下:
- FSMC外擴SRAM實例
實驗要求:配置STM32F407的FSMC以支持驅動IS61LV25616 SRAM芯片的讀寫。
SRAM電器接線圖如下
實驗步驟:
1.配置RCC
2.配置FSMC管腳
3.配置FSMC協議
tRC =ADDSET + DATAST,tRC是SRAM芯片的一個需求時間,可以通過查詢芯片手冊得到tRC的最小滿足時間。IS61LV25616的型號有IS61LV25616-8、IS61LV25616-10、IS61LV25616-12、IS61LV25616-15,分別代表高速訪問時間為8,10,12,15ns,本實驗中以-10為例可知tRC最小時間為10ns ,tDOE最大時間為5ns,在配置是應當將tRC的值設置為大于10ns
tDOE =DATASET,tDOE就是讀使能信號拉低之后到得到有效數據之間的時間。
ADDSET一般是地址發出后到讀使能信號開始拉低的時間,對于SRAM未作要求,大于0即可。FSMC的主頻為168M,一個周期約等于6ns
4.編寫代碼
//main.c
#define SRAM_BANK_ADDR ((uint32_t)0x68000000)//外擴存儲器首地址
int main(void)
{
uint8_t *p = (uint8_t *)SRAM_BANK_ADDR ;
uint8_t i;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_FSMC_Init();
printf("this is fsmc sram testn");
//寫內存
for(i=1; i<=10; i++)
{
p[i] = i;
}
//讀內存
for(i=1; i<=10; i++)
{
printf("p[%d] = %dn",i, p[i] );
}
while (1)
{
Remote_Infrared_KeyDeCode();
}
}
二、觸摸屏專題講解
- 觸摸屏簡介
觸摸屏又稱觸控面板,它是一種把觸摸位置轉化成坐標數據的輸入設備,可以響應用戶點擊的位置(對點擊事件進行響應),是典型的人交互設備,使得人機交互更加人性化。根據觸摸屏的檢測原理,主要分為電阻式觸摸屏和電容式觸摸屏。
電阻觸摸屏:電阻屏造價便宜,能適應較惡劣的環境,但它只支持單點觸控(一次只能檢測面板上的一個觸摸位置,不管有多少觸點同時被觸控,一次只檢測一個),觸摸時需要一定的壓力,使用久了容易造成表面磨損,影響屏幕壽命。
電容觸摸屏:具有支持多點觸控(如玩手機游戲時可以同時有多個控鍵發生作用)、檢測精度高的特點,電容屏通過與導電物體產生的電容效應來檢測觸摸動作,只能感應導電物體的觸摸,濕度較大或屏幕表面有水珠時會影響電容屏的檢測效果。
目前電容式觸摸屏被大部分應用在智能手機、平板電腦等電子設備中,而在汽車導航、工控機等設備中電阻式觸摸屏仍占主流。
觸摸屏與顯示屏的關系:觸摸屏實際上是在液晶屏上面貼了一層大小相等的透明的薄膜,這個薄膜能夠感知觸碰,根據薄膜反饋的觸摸位置,我們就能知道用戶觸碰在屏幕的什么位地方了。為了美觀,電路一般都鋪設在顯示屏的邊框上進行流通,最終通過fpc排線流出,所以絕對的無邊框屏幕時不存在的。
- 電容觸摸屏檢測原理
與電阻式觸摸屏不同,電容式觸摸屏不需要通過壓力使觸點變形。它的基本原理是利用充電時間檢測電容大小,若手指觸摸屏幕,會影響觸摸點附近兩個電極之間的耦合,從而改變兩個電極之間的電容量,若檢測到某電容的電容量發生了改變,即可獲知該電容處有觸摸動作從而通過檢測出電容值的變化來獲知觸摸信號。
簡單理解為么一個電容容量大小固定,充電時間不變。但是手指觸摸時,由于手指的導電性,會有一部分電流流過手指使得電容的充電速度變慢,充電時間變長,由充電時間發生變化可檢測到觸點的位置。
- 電阻觸摸屏檢測原理
電阻式的觸摸屏結構主要由表面硬涂層、兩個ITO層、間隔點以及玻璃底層構成,這些結構層都是透明的,整個觸摸屏覆蓋在液晶面板上,透過觸摸屏可看到液晶面板。表面涂層起到保護作用,玻璃底層起承載的作用,而兩個ITO層是觸摸屏的關鍵結構,它們是涂有銦錫金屬氧化物的導電層。兩個ITO層之間使用間隔點使兩層分開,當觸摸屏表面受到壓力時,表面彎曲使得上層ITO與下層ITO接觸,在觸點處連通電路。
我們可以把上面的ITO層看作X層,下面的ITO層看作Y層,每一層都會加上一個電壓,ITO層是均勻的導電物質,那么整個一層的電阻是呈線性排布,不同地方的電阻大小不同。流過ITO層的電流恒定不變,假設對X層和Y層都加上一個5V電壓,當有觸控時,上下兩層檢測到觸點的電壓都為2.5V,那么理想狀態下觸點為中心,假設上層檢測到觸點的電壓3V,那么上層觸點在順著電壓方向屏幕的3/5處,下層檢測到觸點的電壓2V,下層觸點在順著電壓方向屏幕的2/5處,這樣就相當于確定了一個坐標,從而確定觸點的位置。
兩個ITO涂層的兩端分別引出X-、X+、Y-、Y+四個電極,這是電阻屏最常見的四線結構,通過這些電極,外部電路向這兩個涂層可以施加勻強電場或檢測電壓。
- 電阻觸摸屏校準
電阻觸摸屏的校準就是確定位置和對應的電壓值之間的比例關系。假設添加X的場電壓為5V,理想狀體下,我們希望檢測x方向最左側為0V,最右側為5V,但是實際上最左側可能為大于0V,最右側小于5V,但是ITO層在電壓方向的電壓變化仍是線性關系,那么最左側的電壓值稱為偏移值,線性關系稱為斜率。Y層同理。
X0 = xfac*ADC_X + xoff,xfac是斜率, xoff是偏移值
Y0 = yfac*ADC_Y + yoff,yfac是斜率, yoff是偏移值
- 觸摸控制芯片XPT2406
該芯片將觸點的電壓值數據通過SPI的方式傳輸到MCU,可以看到,紅框內部由時鐘線、片選線、數據輸入、數據輸出,這是典型的SPI傳輸結構。除此之外還有一個中斷管腳PENIRQ,當觸控一個點后,中斷管腳發生作用,告訴MCU內部的轉換數據已經準備好,讓MCU進行讀數據。
- 觸摸屏操作實例
實驗內容:在觸點處顯示圓圈(本次涉及到的液晶屏代碼將在后續講解,本次內容很少而且簡單,不影響閱讀)
說明:當觸摸屏有觸點按下時,PENIRQ引腳會輸出低電平,說明數據已經準備好。直到沒有觸摸的時候,它才會輸出高電平。因此可以設置低電平中斷,在中斷處理過程中向控制芯片發送讀數據的命令并讀取數據。
硬件連線如下,其中INT管腳與中斷管腳及逆行連接,用于觸發中斷。
XPT2406命令字(控制字節)如下:
位3選擇0,說明讀取的數值位12位分辨率,即讀取的電壓值位12位。發送命令后應當連續發送兩個空命令(一個命令8位)來獲得數據。至于為什么要繼續發送兩個空命令,具體原因見本公眾號SPI文章傳輸原理部分。
步驟:(部分配置省略,只進行重要配置說明)
1.配置RCC
2.配置iSPI2
3.配置中斷:將PG7管腳配置為中斷模式
4.編寫代碼
//main.c
#include "spi.h"
#include "gpio.h"
#include "Touch.h"
#include "lcd.h"
//校驗參數(計算得出)
Pen_Holder Pen_Point ={
.xfac = 0.259067,
.xoff = -20,
.yfac = 0.158228,
.yoff = -38,
};
void touch_adjust()
{
Draw_Circle(50, 50, 5,Yellow) ;
Draw_Circle(100, 100, 5,Yellow) ;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_FSMC_Init();
MX_SPI2_Init();
MX_USART1_UART_Init();
printf("this is touch lcd testn");
lcd_init();
lcd_clear(Blue);
//touch_adjust();
while (1)
{
if( Pen_Point.Key_Sta==Key_Down ) //觸摸屏被按下
{
HAL_Delay(20); //按下之后會有抖動,因此延時
if(!HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_7)) //確實按下
{
HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);//關閉中斷
//如果不關可能會因為一些毛刺而再次觸發中斷
printf("++++++touch+++++rn");
if( Convert_Pos() )
//存放點擊的X,Y坐標值,成功返回1失敗返回0
{
printf("x = %d,y = %drn",Pen_Point.X0,Pen_Point.Y0);
//畫一個以X0,Y0為圓心,半徑為10,顏色為黃色的圓
Draw_Circle(Pen_Point.X0, Pen_Point.Y0, 10,Yellow) ;
//恢復為0,以便存放下次觸發位置
Pen_Point.X0 = 0;
Pen_Point.Y0 = 0;
}
HAL_Delay(200);//跳出毛刺
//再次開啟中斷以便下次觸發
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
}
Pen_Point.Key_Sta=Key_Up;
}
}
}
說明:關于程序中的校驗參數怎么得到。我們可以看到程序中有touch_adjust()這樣一個函數,這個函數的功能就是在屏幕上分別畫以(50,50)(100,100)為中心,半徑為5的圓,在程序初始化時就調用此函數,之后用觸筆分別點擊這兩個圓,將每個圓點擊后檢測到的X和Y電壓值帶入二元一次方程。解方程即可得參數。
//gpio.c
//中斷處理函數
extern Pen_Holder Pen_Point;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_7)
{
Pen_Point.Key_Sta = Key_Down;
}
}
//Touch.h
#ifndef _TOUCH_H_
#define _TOUCH_H_
#include "stm32f4xx.h"
//ADS7843/7846/UH7843/7846/XPT2046/TSC2046 指令集
#define CMD_RDY 0X90 //0B10010000即用差分方式讀Y坐標
#define CMD_RDX0XD0 //0B11010000即用差分方式讀X坐標
/*按鍵狀態*/
#define Key_Down 0x01
#define Key_Up 0x00
/* 筆桿結構體 */
typedef struct
{
uint16_t X0; //原始坐標
uint16_t Y0;
uint16_t ADC_X; //X方向的電壓值
uint16_t ADC_Y;//Y方向的電壓值
uint8_t Key_Sta;//筆的狀態
//觸摸屏校準參數
float xfac;
short xoff; //X方向的電壓值偏移值
float yfac;
short yoff;
}Pen_Holder;
//宏定義,用于片選
#define SPI_TOUCH_CS_LOW() HAL_GPIO_WritePin(GPIOG, GPIO_PIN_15, GPIO_PIN_RESET)
#define SPI_TOUCH_CS_HIGH() HAL_GPIO_WritePin(GPIOG, GPIO_PIN_15, GPIO_PIN_SET)
uint8_t Convert_Pos(void);
#endif
//Touch.c
#include "stm32f4xx.h"
#include "Touch.h"
#include "spi.h"
extern Pen_Holder Pen_Point; /* 定義筆實體 全局*/
extern SPI_HandleTypeDef hspi2;
/*封裝了一個發送命令的函數*/
static uint8_t WR_Cmd(uint8_t cmd)
{
uint8_t Tx_DATA[1] = {cmd};//將命令放到Tx_DATA中
uint8_t Rx_DATA[1] = {0};//Rx_DATA用來接收返回值
HAL_SPI_TransmitReceive( &hspi2,Tx_DATA,Rx_DATA,1,0x1000);
return Rx_DATA[0];
}
//從SPI讀數據
//從7846/7843/XPT2046/UH7843/UH7846讀取adc?值
/*************************************************/
/* 功 能:讀取X軸或Y軸的ADC值*/
/* 入口參數:CMD命令 */
/*************************************************/
uint16_t XPT_Read_AD(uint8_t CMD)
{
uint16_t NUMH,NUML;
uint16_t Num = 0;
SPI_TOUCH_CS_LOW(); // CS=0 開始SPI通信
WR_Cmd(CMD);
HAL_Delay(1); //延時等待轉換完成
NUMH=WR_Cmd(0x00);
NUML=WR_Cmd(0x00); //發送兩次空命令來獲取電壓值
Num=((NUMH)< 8)+NUML;
Num >?>=4; //只有高12位有效
SPI_TOUCH_CS_HIGH(); // CS=1 結束通信
//printf("num = %dn", Num);
return(Num);
}
#define READ_TIMES 10 //讀取次數
#define LOST_VAL 4 //丟棄值
/*************************************************/
/*功 能:讀取X軸或Y軸的ADC值 */
/*入口參數:CMD_RDX:讀取X的ADC值 */
/* CMD_RDY:讀取Y的ADC值 */
/*說 明:與上一個函數相比,這個帶有濾波,多讀幾個數據,去掉一些后求平均*/
/*************************************************/
uint16_t XPT_Read_XY(uint8_t xy)
{
uint16_t i, j;
uint16_t buf[READ_TIMES];
uint16_t sum=0;
uint16_t temp;
for(i=0;i< READ_TIMES;i++)
{
buf[i]=XPT_Read_AD(xy);
}
for(i=0;i< READ_TIMES-1; i++)//排序
{
for(j=i+1;j< READ_TIMES;j++)
{
if(buf[i] >buf[j])//升序排列
{
temp=buf[i];
buf[i]=buf[j];
buf[j]=temp;
}
}
}
sum=0;
for(i=LOST_VAL;i< READ_TIMES-LOST_VAL;i++)
sum+=buf[i];
temp=sum/(READ_TIMES-2*LOST_VAL);
return temp;
}
/*************************************************/
/*功 能:讀取X軸和Y軸的ADC值 */
/*入口參數:Pen_Point.X_ADC,&Pen_Point.Y_ADC */
/*出口參數:0:成功(返回的X,Y_ADC有效) */
/* 1: 失敗(返回的X,Y_ADC無效) */
/*************************************************/
uint8_t Read_XPT(uint16_t *x,uint16_t *y)
{
uint16_t xtemp,ytemp;
xtemp=XPT_Read_XY(CMD_RDX);
ytemp=XPT_Read_XY(CMD_RDY);
if(xtemp< 100||ytemp< 100)
return 1;//讀取失敗
*x=xtemp;
*y=ytemp;
return 0;//讀取成功
}
/*************************************************/
/*功 能:連續兩次讀取ADC值 */
/*原 理: 把兩次讀取的值作比較,在誤差范圍內可取 */
/*入口參數:Pen_Point.X_ADC,&Pen_Point.Y_ADC */
/*出口參數:0:成功(返回的X,Y_ADC值有效) */
/* 1: 失敗(返回的X,Y_ADC值無效) */
/*************************************************/
#define ERR_RANGE 50 //誤差范圍
uint8_t Read_XPT2(uint16_t*x,uint16_t *y)
{
uint16_t x1,y1;
uint16_t x2,y2;
uint8_t res;
res=Read_XPT(&x1,&y1); //第一次讀取ADC值
if(res==1)
return(1);//讀取失敗返回1
HAL_Delay(3);
res=Read_XPT(&x2,&y2);//第二次讀取ADC值
if(res==1)
return(1); //讀取失敗返回1
//前后兩次采樣在±50內
if(((x2<=x1&&x1< x2+ERR_RANGE)||(x1<=x2&&x2< x1+ERR_RANGE))
&&((y2<=y1&&y1< y2+ERR_RANGE)||(y1<=y2&&y2< y1+ERR_RANGE)))
{
*x=(x1+x2)/2;
*y=(y1+y2)/2;
return 0; //正確讀取,返回0
}else return 1; //誤差太大,讀取錯誤
}
//轉換結果
//根據觸摸屏的校準參數來決定轉換后的結果,保存在X0,Y0中
uint8_t Convert_Pos(void)
{
HAL_Delay(8);
if(Read_XPT2(&Pen_Point.ADC_X,&Pen_Point.ADC_Y) ==0 )
{
//printf("adc_x= %d, adc_y = %dn",Pen_Point.ADC_X,Pen_Point.ADC_Y);
Pen_Point.X0=Pen_Point.xfac*Pen_Point.ADC_X+Pen_Point.xoff;
Pen_Point.Y0=Pen_Point.yfac*Pen_Point.ADC_Y+Pen_Point.yoff;
return 1;
}
else return 0;
}
評論
查看更多