第2課 CC2530的通用I/O端口輸入和輸出控制
XMF393 / 廣東職業技術學院 歐浩源
【視頻教程】:將寄存器中某些為清0或置1
【視頻教程】:CC2530通用I/O及相關寄存器
【視頻教程】:LED燈閃爍實現的源碼分析
【視頻教程】:基於CC2530的跑馬燈實現
【視頻教程】:按鍵的工作原理與程序設計思路
【視頻教程】:按鍵控制LED燈開關
【視頻教程】:按鍵控制跑馬燈的運行與暫停
一、CC2530的引腳概述
CC2530微控制器采用QFN40封裝,有40 個引腳。其中,有21個數字I/O端口,其中P0和P1是8 位端口,P2僅有5位可以使用。這21個端口均可以通過編程進行配置。實際上,在P2端口的5個引腳中,有2個需要用作仿真,有2個需要用作晶振,你在CC2530的開發中真正能夠使用的只有17個引腳。
在微控制器內部,有一些特殊功能的存儲單元,這些單元用來存放控制微控制器內部器件的命令、數據或運行過程中的一些狀態信息,這些寄存器統稱為“特殊功能寄存器(SFR)”。操作微控制器的本質,就是對這些特殊功能寄存器進行讀寫操作,並且某些特殊功能寄存器可以位尋址。
每一個特殊功能寄存器本質上就是一個內存單元,而標識每個內存單元的是內存地址,不容易記憶。為了便於使用,每個特殊功能寄存器都會起一個名字,在程序設計時,只要引入頭文件“ioCC2530.h”,就可以直接使用寄存器的名稱訪問內存地址了。
CC2530的通用I/O端口相關的常用寄存器有下面4個:
<1> PxSEL:端口功能選擇,設置端口是通用I/O還是外設功能。
<2> PxDIR:作為通用I/O時,用來設置數據的傳輸方向。
<3> PxINP:作為通用輸入端口時,選擇輸入模式是上拉、下拉還是三態。
<4> Px:數據端口,用來控制端口的輸出或獲取端口的輸入。
二、設置寄存器中某些位的方法
<1> 對寄存器的某些位清0而不影響其他位。
例如:寄存器P1TM的當前值是0x6c,現需要將該寄存器的第1位、第3 位和第5位設置為0,同時不能影響該寄存器其他位的值,那么,在C語言中應該怎么編寫代碼呢?
使用“&=”將寄存器指定位清0,同時不影響其他位的值。
正確寫法:P1TM &= ~0x2A;
因為:邏輯“與”操作的特點是,該位有0結果就為0,若為1則保存原來值不變。
首先將字節 0000 0000 中要操作的位設置為1,即0010 1010,在將該數值取反,即1101 0101,也就是~0x2A。再將該值與寄存器P1TM“相與”,那么有0的位,即1、3、5位將被清0,其余的位會保持原來的值不變。
所以:P1TM的當前值為0x6c,即0110 1100,
0110 1100 && 1101 0101 = 0100 0100,即1、3、5位清0,其他位不變。
<注意>:該方法只能操作多位同時清0,或者某一位清0的情況,如果要將寄存器的位既要清0又要置1,則不能采用這種寫法。(其中原因自己思考一下)
在不少嵌入式應用的源碼程序中,對於寄存器的第n位的清0操作也可以寫成:寄存器 &= ~(0x01<<(n));其道理是一樣的。
<2> 對寄存器的某些位置1而不影響其他位。
例如:寄存器P1TM的當前值是0x6c,現需要將該寄存器的第1位、第4位和第5位設置為1,同時不能影響該寄存器其他位的值,那么,在C語言中應該怎么編寫代碼呢?
使用“|=”將寄存器指定位置1,同時不影響其他位的值。
正確寫法:P1TM |= 0x32;
因為:邏輯“或”操作的特點是,該位有1結果就為1,若為0則保存原來值不變。
首先將字節 0000 0000 中要操作的位設置為1,即0011 0010,也就是0x32。 再將該值與寄存器P1TM“相或”,那個有1的位,即1、4、5位將被設置為1,其余的位會保持原來的值不變。
所以:P1TM的當前值為0x6c,即0110 1100,
0110 1100 || 0011 0010 = 0111 1110,即1、4、5位置1,其他位不變。
同樣要注意:該方法只能操作多位同時置1,或者某一位置1的情況。
對於寄存器的第n位的清0操作也可以寫成:寄存器 |= (0x01<<(n));
三、實訓案例:按鍵輸入控制燈光輸出狀態
【1】准備工作。
引入CC2530必要的頭文件“ioCC2530.h”,定義相關變量等。
【2】端口功能選擇。
微控制器的大部分I/O端口都是功能復用的,在使用的時候需要通過功能選擇寄存器來配置端口的功能。
【3】端口傳輸方向設置。
【4】對於輸入的端口要設置其輸入方式。
輸入方式用來從外界器件獲取輸入的電信號,當CC2530的引腳為輸入端口時,該端口能夠提供“上拉”、“下拉”和“三態”三種輸入模式,可以通過編程進行設置。在本次實訓中,實際上不需要對P0_1和P1_2引腳進行輸入方式的設置,因為CC2530復位后,各個I/O端口默認使用的就是上拉模式。
【5】通用I/O端口寄存器配置的基本思路。
【6】設計端口初始化函數InitPort()。
<1>設置P1SEL寄存器,將P1_2、P1_3和P1_4設置為通用I/O端口。
<2>設置P1DIR寄存器,將P1_3和P1_4設置為輸出,將P1_2設置為輸入。
<3>設置P0SEL寄存器,將P0_1設置為通用I/O端口。
<4>設置P0DIR寄存器,將P0_1設置為輸入。
<5>設置PxINP寄存器,將P0_1和P1_2設置為上拉模式,也可以不設置。
【7】設計鍵盤掃描函數ScanKeys()。
<1>沒有按鍵下時,端口的輸入為高電平,當發現該端口有低電平產生時,則有可能會是按鍵按下,需要經過去抖動處理,如果該端口還是低電平,則確認為按鍵按下。
<2>在進行按鍵處理時,先等待按鍵松開,然后再將相關的LED進行開關狀態的取反控制。
【8】主函數的實現。
至此,大功搞成,連接仿真器,進行編譯調試。
【附件】:本實訓源代碼。
#include "ioCC2530.h" #define LED5 P1_3 #define LED6 P1_4 #define SW1 P1_2 #define SW2 P0_1 /*===================延時函數=========================*/ void Delay(unsigned int t) { while(t--); } /*================端口初始化函數======================*/ void InitPort() { P1SEL &= ~0x18; //將P1_3和P1_4設置為通用I/O端口功能 P1DIR |= 0x18; //將P1_3和P1_4的端口傳輸方式設置為輸出 P1SEL &= ~0x04; //將P1_2設置為通用I/O端口功能 P1DIR &= ~0x04; //將P1_2的端口傳輸方式設置為輸入 P0SEL &= ~0x02; //將P0_1設置為通用I/O端口功能 P0DIR &= ~0x02; //將P0_1的端口傳輸方式設置為輸入 P0INP &= ~0x02; //將P0_1的端口輸入方式設置為:上拉/下拉 P1INP &= ~0x04; //將P1_2的端口輸入方式設置為:上拉/下拉 P2INP &= ~0x60; //將P0端口和P1端口引腳設置為:上拉 LED5 = 0; //上電的時候,LED5不亮 LED6 = 0; //上電的時候,LED6不亮 } /*=================按鍵掃描函數=======================*/ void ScanKeys() { if(SW1 == 0) { //發現SW1有低電平信號 Delay(100); //按鍵去抖動 if(SW1 == 0) { //確實是有按鍵動作 while(SW1 == 0); //等待按鍵1松開 //將LED5的燈光開關狀態取反 LED5 = ~LED5; } if(SW2 == 0) { //發現SW2有低電平信號 Delay(100); //按鍵去抖動 if(SW2 == 0) { //確實是有按鍵動作 while(SW2 == 0); //等待按鍵2松開 LED6 = ~LED6; } } } /*=====================主函數=========================*/ void main() { InitPort(); while(1) { ScanKeys(); } }