AVR單片機學習(五)按鍵與數(shù)碼管的程序設計
作者:zww 1988 來源:本站原創(chuàng) 點擊數(shù):
… 更新時間:2014年04月18日 【字體:
大 中 小】
按鍵與數(shù)碼管的程序設計
AVR IO口的輸入模式與上拉電阻
選擇結構語句與按鍵的查詢方式程序設計
數(shù)碼管基本原理
掃描方式顯示多位數(shù)碼管
一、輸入狀態(tài)IO寄存器設置
1、DDRx
某一位置0,相應位的IO口被設置為輸入
2、PORTx某一位置1,使能對應IO口相應位的上拉電阻
3、PINx的對應位是輸入的數(shù)據(jù),0或1

選擇結構語句
一、關系運算符和關系表達式
小于< 小于等于<=
大于> 大于或等于== 不等于!=
二、邏輯運算符和邏輯表達式
邏輯與&&邏輯或||邏輯非!
三、if 語句結構
if(表達式1)語句1
else
if(表達式2)語句2
else 語句3
四、switch
語句結構
switch(表達式)
{case
常量1:表達式1
case
常量2:表達式2
.........
default:表達式n}

按鍵的查詢方式程序設計
一、PIND & (1<<6)
二、1<<6
1、1左移6位,即:0b01000000
怎么判斷一個按鍵按下了呢?首先看下圖是4個按鍵


第一個是PD2 上一段接VCC 其他都是一段接IO(PD3
PD6 PD7)口另一端接地線。
所以當按鍵閉合時候相應IO都輸入一個0,當按鍵抬起來的時候IO輸入多少呢?
所以這些IO口必須將上拉電阻進行使能,將按鍵打開相當于輸入一個1.所以我們判斷這3個按鍵按沒按下去的話,就判斷輸入是不是0就行了。
對于第一個按鍵如果按下輸入是1,當抬起來時候由于AVR內部不帶下拉電阻的,所以按鍵打開時候輸入是0.
所以就需要判斷某一位是0,還是1.某一位是0還是1就用到了& 與運算了。 1 跟1
與就1 1 與0 就是0
上面代碼(temp& (1<<6))
(temp & 0b01000000) temp本身值不變,只是結果來判斷某一位是0還是1
比如:PD6
上的K3
因為PD6 所以 PIND
&(1<<6)的結果就行了
三、PIND & (1<<6)
1、移除第6位之外其他位清零
2、第6位保持輸入的值
四、與選擇結構語句的結合
1、判斷PIND & (1<<6)的值,執(zhí)行相應代碼

除非你上電之前一直講按鍵按下,否則上電的一瞬間程序就執(zhí)行到while(1);了所以要將他們加入到死循環(huán)里面如下圖

這樣就實現(xiàn)了按鍵的不停的檢測。 其實DDRD 上電默認都是0
所以清0 置為輸入也沒有意義。
程序在if判斷設置斷點然后全速執(zhí)行可以看到只要沒有按鍵按下程序進不去斷點,如果我們在板子上按下K3則如下圖所示進入斷點,再按下單步執(zhí)行蜂鳴器響了如圖。

所以這個程序就達到了我們的目的。
現(xiàn)在換一種判斷 就是按鍵被按下而不是沒被按下 用邏輯非

這樣達到了預想的目標。但是這樣只能判斷一個按鍵如果多個按鍵怎么辦呢?2種辦法
一、采用if elseif else
if (){
elseif{
}
elseif {
}
else{
}
}
二、采用
switch (表達式){
case 相符合的條件
{
break;
}
case 相符合的條件 {
break;
}
default{
}
}
一、用if 實現(xiàn)
#include
int main(void){
//PD6 設置為輸入 K3
DDRD &= ~(1 << 6);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<6);
//PD7 設置為輸入 K4
DDRD &= ~(1 <<
7);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<7);
//PD2 設置為輸入 K1
DDRD &= ~(1 <<
2);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |=
(1<<2);//這個上拉不上拉沒關系因為上拉是百K的電阻所以開關打開還是認為是低電平
//PD3 設置為輸入 K2
DDRD &= ~(1 <<
3);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<3);
//蜂鳴器PA3 設置方向寄存器為輸出
DDRA |= (1<<3);
//蜂鳴器關掉
PORTA &= ~(1<<3);
//流水燈端口全部設為輸出
DDRB = 0xff;
while(1){
//判斷PIND 這位是否為1
為真的話就是按鍵沒有按下
if (!(PIND &
(1<<6))){ //本來沒有按下 進入 現(xiàn)在變成了沒有按下 不進入了取非了
被按下進入了 PD6
//按鍵被按下用蜂鳴器表示一下
PA3
PORTA
|= (1<<3);
}
else if(!(PIND &
(1<<7))){ //PD7
K4按下讓流水燈產生動作 必須上面使能K4上拉電阻
PORTB |=
(1<<0);//第一個燈發(fā)光 就是等于1
}
else if (PIND &
(1<<2)){//因為按下的時候是低電平接的是電源
//第二個燈發(fā)光
PORTB |=
(1<<1);
}
else if (!(PIND &
(1<<3))){
//第三個燈發(fā)光
PORTB |=
(1<<2);
}
else{
//變成了按鍵沒有按下
PORTA &= ~(1<<3); //蜂鳴器
PORTB =
0;//燈
}
//看到沒有按下一直響的,按下就不響了。
}
}
---------------------------------------------------------------
二、用switch 來實現(xiàn)就需要一次性將這四位讀回來。 代碼如下
#include
int main(void){
//PD6 設置為輸入 K3
DDRD &= ~(1 << 6);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<6);
//PD7 設置為輸入 K4
DDRD &= ~(1 <<
7);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<7);
//PD2 設置為輸入 K1
DDRD &= ~(1 <<
2);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |=
(1<<2);//這個上拉不上拉沒關系因為上拉是百K的電阻所以開關打開還是認為是低電平
//PD3 設置為輸入 K2
DDRD &= ~(1 <<
3);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<3);
//蜂鳴器PA3 設置方向寄存器為輸出
DDRA |= (1<<3);
//蜂鳴器關掉
PORTA &= ~(1<<3);
//流水燈端口全部設為輸出
DDRB = 0xff;
while(1){
//首先一次性將這4個位都讀回來
2 3 6 7 腳
switch(PIND
& 0b11001100) {
case
0b11001100: {//只有第一個按鍵按下 0b11001100
接的電源按下是1
//LED
0 發(fā)光
PORTB
|= (1<<0);
break;
}
case
0b11000000: {//只有第 二個按鍵按下 0b11001100
接的電源抬起是0
//LED
0 發(fā)光
PORTB
|= (1<<1);
break;
}
case
0b10001100: {//只有第三個按鍵按下 0b11001100
接的電源抬起是0
//LED
0
PORTB
|= (1<<2);
break;
}
case
0b01001100: {//只有第四個按鍵按下 0b11001100
接的電源抬起是0
//LED
0 發(fā)光
PORTB
|= (1<<3);
break;
}
default
:{ //都沒有按下
0b11001000 因為有下拉
//變成了按鍵沒有按下
PORTA &= ~(1<<3); //蜂鳴器
PORTB = 0;//燈
break;
}
}
//判斷PIND
這位是否為1 為真的話就是按鍵沒有按下
}
}
-------------------------------------------------------------------------
以上都是查詢方式因為都是在while循環(huán)一邊一邊的查詢,按鍵有動作就執(zhí)行相應的代碼這樣很耽誤CPU的時間的,在下一篇博客我會稍微降講用中斷的方式來編寫按鍵的程序。下面繼續(xù)說呵呵、
八段數(shù)碼管
一、八段數(shù)碼管
1、八段數(shù)碼管由八段LED構成
2、各LED陰極或陽極并在一起,稱為“位選線”:共陰、共陽
3、其余8個引腳各自引出,稱為“段選線”,各段可以分別控制



記住一般一位的數(shù)碼管有10個腳
個人理解:( 其中2腳是連在一起的是公共端。其他8個是段選
比如1、6接電源 其他接IO口個人理解的)
多位合一的數(shù)碼管
一、多位合一的數(shù)碼管
1、將多個八段數(shù)碼管的段選線分別并在一起,位選線引出如下圖

由上圖看出是4位 應該是8個段選線(7段加一個點) 4個位選線
共12根線 。
com0 ---- com3 是位選
a-g 加 dp
是段選。
多位數(shù)碼管的使用
1、多位數(shù)碼管的各個位均可以單獨顯示不同的數(shù)據(jù),但一個時刻只能點亮一位、(點快點人眼看不出來)
2、依次點亮多位數(shù)碼管中的各個位,由于人眼的視覺暫留效應,看起來是同時點亮
3、如下圖是電路圖 硬件電路是下圖設計的

它的每一段相當于一個發(fā)光二極管,電流大約是10個mA左右(5--10)mA,因此段選可以直接用單片機的IO驅動是足夠的不論是拉電流還是灌電流,這里面我們用的是一個共陰極的數(shù)碼管,因此應該是向外拉電流,而段選線我們可以計算下段選線上最大電流時多少?假設每段都點亮沒段是10mA的話,那么位選線上也就是10*8
= 80mA
所以我們不能用IO口,一般的單片機不可能輸出這么大的電流,所以我采用一個三極管來進行驅動,共陰極的數(shù)碼管一般要用NPN型的數(shù)碼管,它的接法如下圖的樣子。

再來張清楚點的下圖


可以看到C0
接的是COM0 位選線,IO口通過1K電阻接到三極管基極上,如果IO是個高電平的話電流就通過三極管到射極流下來的,因此三極管達到飽和,CO點相當于導通相當于接地。4個段選分別接到PA4到PA7
四個IO口上因此我們寫程序首先將PA4 輸出一個1 PA5 PA6 PA7 全都輸出0
這樣我們選中第0個第一位數(shù)碼管此時在PB口上輸出的數(shù)據(jù)就會顯示在數(shù)碼管上面。編寫程序:

設置一個斷點然后再單步調試(F10)?纯此@示的是那一段。同時流水燈也亮了,因為是同一個IO口。這樣對應PB上的每一段都找到了。
好了這樣我們就去編寫一下數(shù)碼管的段碼;
首先是顯示1
只要將需要點亮的各個段置1就實現(xiàn)了段碼的功能,具體的編寫過程自己去畫畫看

這是我自己用數(shù)組的形式定義的。
首先是什么類型的數(shù)組 名稱 元素個數(shù)
0-9
A-F
全部顯示出來就是16個元素,一個字符型數(shù) 加一個逗號分開。一直放16個,使用時候要從第0個開始下標從0開始的。
#include
int main(void){
//PD6 設置為輸入 K3
DDRD &= ~(1 << 6);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<6);
//PD7 設置為輸入 K4
DDRD &= ~(1 <<
7);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<7);
//PD2 設置為輸入 K1
DDRD &= ~(1 <<
2);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<2);//這個上拉不上拉沒關系
因為上拉是百K的電阻所以開關打開還是認為是低電平
//PD3 設置為輸入 K2
DDRD &= ~(1 <<
3);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<3);
//蜂鳴器PA3 設置方向寄存器為輸出
DDRA |= (1<<3);
//蜂鳴器關掉
PORTA &= ~(1<<3);
//數(shù)碼管全部置為輸出
DDRB = 0xff;
//位選線高四位全部置1 也是輸出 因為是置其中4位所以用|=
DDRA |= 0Xf0;
char scandata[16]={
0b10101111,//0
0b10100000,//1
0b11000111,//2
0b11100110,//3
0b11100000,//4
0b01101110,//5
0b01101111,//6
0b10100010,//7
0b11101111,//8
0b11101110,//9
0b11100111,//A
0b01101101,//b
0b00001111,//c
0b11000001,//d
0b01001111,//E
0b01001111//F
};
while(1){
//數(shù)碼管也需要掃描所以也用死循環(huán)
while(1)
//先將數(shù)碼管的第一位點亮
選中位選
PORTA
|=(1<<4);
//再將數(shù)據(jù)送到PB口上哪一段對應哪一位
要事先測量下 編一個程序測量下
PORTB =
scandata[0];//顯示0
PORTB =
scandata[1];//顯示1
PORTB =
scandata[2];//顯示2
PORTB =
scandata[3];//顯示3
PORTB =
scandata[4];//顯示4
PORTB =
scandata[5];//顯示5
PORTB =
scandata[6];//顯示6
PORTB =
scandata[7];//顯示7
PORTB =
scandata[8];//顯示8
PORTB =
scandata[9];//顯示9
PORTB =
scandata[10];//顯示A
PORTB =
scandata[11];//顯示B
PORTB =
scandata[12];//顯示C
PORTB =
scandata[13];//顯示D
PORTB =
scandata[14];//顯示E
PORTB =
scandata[15];//顯示F
}
}
//這就是數(shù)碼管用段碼顯示。那么怎么對數(shù)碼管掃描顯示呢?
我們可以遵循這樣一個順序,首先將數(shù)碼管位選中,送數(shù)據(jù) PORTA
選中
PORTB送上數(shù)據(jù)然后打開相應這一位。讓他顯示出來,顯示出來之后呢?再讓這一位熄滅可以把所有四位都熄滅
位選 PORTA &0x0f;高四位清0這樣就完成了數(shù)碼管的高四位顯示。
#include
int main(void){
int j ;
//PD6 設置為輸入 K3
DDRD &= ~(1 << 6);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<6);
//PD7 設置為輸入 K4
DDRD &= ~(1 <<
7);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<7);
//PD2 設置為輸入 K1
DDRD &= ~(1 <<
2);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<2);//這個上拉不上拉沒關系
因為上拉是百K的電阻所以開關打開還是認為是低電平
//PD3 設置為輸入 K2
DDRD &= ~(1 <<
3);
//輸入狀態(tài)下將數(shù)據(jù)寄存器使能上拉電阻
PORTD |= (1<<3);
//蜂鳴器PA3 設置方向寄存器為輸出
DDRA |= (1<<3);
//蜂鳴器關掉
PORTA &= ~(1<<3);
//數(shù)碼管全部置為輸出
DDRB = 0xff;
//位選線高四位全部置1 也是輸出 因為是置其中4位所以用|=
DDRA |= 0Xf0;
char scandata[16]={
0b10101111,//0
0b10100000,//1
0b11000111,//2
0b11100110,//3
0b11100000,//4
0b01101110,//5
0b01101111,//6
0b10100010,//7
0b11101111,//8
0b11101110,//9
0b11100111,//A
0b01101101,//b
0b00001111,//c
0b11000001,//d
0b01001111,//E
0b01001111//F
};
while(1){
//數(shù)碼管也需要掃描所以也用死循環(huán)
while(1)
//先將數(shù)碼管的第一位點亮
選中位選
PORTA
|=(1<<4);
//再將數(shù)據(jù)送到PB口上哪一段對應哪一位
要事先測量下 編一個程序測量下
PORTB =
scandata[0];//顯示0
for
(j=0;j<400;j++);//延時
PORTA &=
0x0f;
PORTA |=
(1<<5);
PORTB =
scandata[1];
PORTA &=
0x0f;
for
(j=0;j<400;j++);//延時
PORTA |=
(1<<6);
PORTB =
scandata[2];
PORTA &=
0x0f;
for
(j=0;j<400;j++);//延時
PORTA |=
(1<<7);
PORTB =
scandata[3];
for
(j=0;j<400;j++);//延時
PORTA &=
0x0f;
//單步仿真看看
就是1位亮了0 熄滅 2位亮1 熄滅
3位亮2 熄滅 4位亮3
熄滅 一直循環(huán)
//全速執(zhí)行看看顯示4個數(shù)字
可以看到亮度不怎么亮的,因為 點亮熄滅只有那么一小段時間是發(fā)光的
//所以如果要增加亮度只需要加一個延時程序
聲明一個變量j 在看效果 全速執(zhí)行亮度是明顯增加了
//這就是數(shù)碼管程序掃描的程序設計
}
}