1. I/O 口的结构及特点
x为B或C 或D,分别代表B口、C口或D 口;
n为0~7,代表寄存器中的位置;
其中DDRx 和PORTx 是可读写寄存器,而PINx 为只读寄存器;
每个I/O引脚内部都有独立的上拉电阻电路,可通过程序设置内部上拉电阻是否有效。
方向寄存器DDRx中的每个位用于控制I/O口一个引脚的输入输出方向,即控制I/O口的工作模式为输出模式还是输入模式。
若PORTxn=1时,I/O引脚呈高电平,同时可提供输出20mA 的电流;
若PORTxn=0时,I/O引脚呈低电平,同时可吸收20mA 的电流。
引脚寄存器PINxn 中的数据就是外部引脚的实际电平;
此时可通过PORTxn 的设置可控制内部的上拉电阻使用或不使用。
注意事项:
2. 跑马灯程序控制发光二极管
我们选择用PD0~PD7来控制8个发光二极管循环点亮,从而实现“跑马灯”
所以电路图如图所示:
C程序如下:
1 //延时1MS
2 void delay_1ms()
3 {
4 unsigned int i;
5 for(i=1;i<(unsigned int)(1144-2);i++)
6 ;
7 }
8 //延时nMS
9 void delay_ms(unsigned int n)
10 {
11 unsigned int i=0;
12 for(i=0;i<n;i++)
13 {
14 delay_1ms();
15 }
16 }
17 //主函数,依次顺序打开LED
18 int main()
19 {
20 unsigned char i;
21 DDRD = 0xFF; //设置D口为输出模式
22 PORTD = 0xFF; //置高电平
23 while(1)
24 {
25 for(i=0;i<8;i++) //顺序打开LED
26 {
27 PORTD = ~(1<<i); //点亮的位置低电平
28 delay_ms(200);
29 }
30 }
31 return 0;
32 }
问:如果改变 delay_ms 的时间会变的怎样呢?
=> 跑马灯的变换速度改变。
问:如果直接让 PORTD = ( 1<<i )呢?
=> PORTD = ~( 1<<i ):每次只亮一个灯;
=> PORTD = ( 1<<i ):每次只有一个灯是不亮的,其余灯都亮着。
问:能不能添加或修改程序,改变灯亮的顺序和时间呢?
=> 改变循环条件就可以改变亮灯的顺序,如把i++改为i+=2,即可实现隔一盏灯亮一个;
=> 亮灯时间改变延迟时间delay_ms()的传入参数即可。
3. 单个LED数码管练习
给数码管的a、b、c、d、e、f、g七个发光二极管加不同的电平,二极管显示不同亮暗的组合就可以显示不同的字形;
以1为高电平,0为低电平,给出字形码表:
即0x3F表示的就是字型‘0’,0x06表示的就是字型‘1’......;
所以直接把这种对应关系存到一个Char型数组里(一个Char型是8位);
想要对应的a、b、c、d、e、f、g七个发光二极管展示亮与暗,我们选用PD0~7这8位来控制;
如:想要展示字型‘0’ => ‘0’对应字形码是0x3F => 其中发光二极管的a~f均为亮状态 => PD0~5均为低电平(低电平亮灯)。
我们用一个LED数码管显示数字0~9,仍然使用PD口控制,循环显示数字
电路图如图所示:
了解理论知识后便可以开始编程:(延时函数与上面相同)
1 void main()
2 {
3 unsigned char i;
4 unsigned char num[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
5
6 DDRD = 0xFF; //设置D口为输出模式
7 PORTD = 0xFF; //置高电平
8
9 while(1)
10 {
11 for(i=0;i<10;i++) //LED数码管顺序显示字型0~9
12 {
13 PORTD = num[i];
14 delay_ms(500);
15 }
16 }
17 }
问:想显示A~F 怎么办呢?
=> num[] 数组再添加多几个元素,分别是A~F对应的字形码。
问:能不能显示像H,L 一类的字母呢?
=> 能,想要显示字母H,只需要二极管的b、c、e、f、g亮即可;
=> 想要显示字母L,只需要二极管的d、e、f亮即可。
问:改变了delay_ms 函数的延时时间会怎样呢?
=> 数字之间的显示间隔边长。
问:要显示小数点我们应该怎么办呢?
=> 在字形码表中可以看出,最高位h是没有作用的,所以可以用h位来控制小数点,当最高位为1时小数点亮,为0时不亮;
=> 如0x5B表示‘2’,那么0x5B + 0x80表示‘2.’。
4.多个LED数码管实验
静态显示:3小节的内容便是静态展示
动态显示:采用各数码管循环轮流的显示的方法,当循环频率较高时,利用人眼的暂留特性,感觉不到数码管的闪烁,就像看到数码管在同时发光一样,类似电影的原理。
两者对比:动态显示比静态显示占用资源少,耗电少;但是稳定性却差,程序设计也更为复杂,MCU负担重。
动态显示需要一个接口完成字形码的输出,另外一个接口完成各数码管的轮流显示;
我们要实现从“000.0”到“999.9”的数字变化显示过程;
用PB口做字形码的输出口,用PC0~PC3控制数码管的轮转流显示;
从左数起,其中PC0表示第0个数,PC1表示第1个数,PC2表示第3个数(该数显示小数点),PC3表示第4个数;所以只有第三个数是特殊的;
参考原理图如下:
代码实现如下:
1 void main()
2 {
3 //PB口做字形码的输出口,PC0~PC3控制数码管的轮流显示
4 unsigned char i,j;
5 static unsigned char LedNum[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
6 //显示小数点要加0x80
7 unsigned char CountNum[4] = {0,0,0,0}; //计数的百、十、个位和十分位
8 DDRB = 0xFF; //设置B口为输出模式
9 PORTB = 0xFF; //置高电平
10 DDRC = 0x0F;
11 PORTC = 0xFF;
12
13 while(1)
14 {
15 i=0; //用于PC0~PC3的轮转
16 for(j=0;j<10;j++) //依次显示4S"000.0"
17 {
18 i = ++i%4;
19 PORTC&=~(1<<i);
20 if(2==i) //如果是第三个数字则加个小数点显示
21 PORTB = LedNum[0] + 0x80;
22 else //其余情况都没有小数点显示
23 PORTB = LedNum[0];
24 delay_ms(10);
25 PORTC|= 0xFF;//复位,切换到下一个输出口
26 }
27 CountNum[0] = CountNum[1] = CountNum[2] = CountNum[3] = 0; //用于实现4位数的加一变化
28 while(1)
29 {
30 //计数加1,满9进位、后置0
31 if(CountNum[3]++==9)
32 {
33 CountNum[3] = 0;
34 if(9 == CountNum[2]++)
35 {
36 CountNum[2] = 0;
37 if(9 == CountNum[1]++)
38 {
39 CountNum[1] = 0;
40 CountNum[0]++;
41 }
42 }
43 }
44 //显示计数值
45 for(i=0;i<4;i++)
46 {
47 PORTC&=~(1<<i);
48 if(2 == i)
49 PORTB = LedNum[CountNum[i]] + 0x80;
50 else
51 PORTB = LedNum[CountNum[i]];
52 delay_ms(10);
53 PORTC|=0xFF;
54 }
55 //999.9的时候退出
56 if(CountNum[0]==9 && CountNum[1]==9 && CountNum[2]==9 && CountNum[3]==9)
57 break;
58 }
59 }
60 }