Featured image of post 单片机课程设计

单片机课程设计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
单片机课设任务比较多,我这几天还在医院,说实话本来想抄抄代码自己就debug一下的
没想到还蛮好玩的
借鉴别人代码的时候发现有些朋友写的实在是一言难尽
基本没有模块化,变量命名等格式要求也不是很规范
遂自己写库文件,把几个常用的函数封装了一下

不禁让我想起,之前做数学动画用manim
原作者也是需要经常编程,但不从事软件开发相关工作的人
移植manim个人版项目的时候为了配环境搞的焦头烂额,太不规范了
后来搞出来一些之后就没有再探索了,等开源社区2.0版本出来再搞
代码素养真的很重要,方不方便移植和理解会直接影响一个项目的价值

实验1:简单 IO 实验

任务

  1. 控制 8 个 LED 依次点亮,模拟流水灯试验。
  2. 利用一个独立式按键控制一个 LED 灯的亮灭,每按 一次键,灯状态改变一次状态,用中断方式实现。
  3. 在一位数码管上逐次静态显示数字 0~9 和字母 A~F。

流程图

  1. LED 流水灯

  1. 独立按键控制 LED 灯亮灭

  1. 数码管显示

程序

  1. LED 流水灯
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <msp430f149.h>
#include "config.h"
#include "uint.h"

void main (void)
{
  WDTCTL=WDTPW+WDTHOLD;   //关闭看门狗
  BoardConfig();          //端口初始化
  Clock_Init();           //时钟初始化
  while(1)
  {
    uint8 led_code=BIT0;  // 0000 0001
    uint8 i;
    for(i=0;i<8;i++)
    {
      P2DIR=0xFF;         //P2流水灯端输出,低电平点亮
      P2OUT=~led_code;    //P2不断翻转,控制亮灭
      delay_ms(1000);     //1s延时
      led_code<<=1;       //持续左移一位
    }
  }
}
  1. 独立按键控制 LED 灯亮灭
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <msp430f149.h>
#include "config.h"

void main(void)
{
  WDTCTL=WDTPW+WDTHOLD;     //关闭看门狗
  BoardConfig();            //端口初始化
  Clock_Init();             //时钟初始化
  P2DIR|=BIT7;              //P2.7输出,控制第8个LED灯
  P1IES|=BIT0;              //下降沿触发中断
  P1IFG&=~BIT0;             //清除P1.0口中断标志
  P1IE|=BIT0;               //P1.0口中断使能,第一个独立按键
  _BIS_SR(LPM3_bits+GIE);   //进入低功耗模式,全局中断
}

#pragma vector=PORT1_VECTOR
__interrupt void PORT1(void)
{
  if(P1IFG&BIT0)            //判断是否为P1.0产生的中断,如果是就执行以下程序
  {
    P2OUT^=BIT7;            //翻转LED灯状态
    while(!(P1IN&BIT0));    //等待按键释放
    delay_ms(100);          //延时,按键去抖
    P1IFG=0;                //清除中断标志位
  }
}
  1. 数码管显示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <msp430f169.h>
#include "config.h"
#include "uint.h"
#include "digit_disp.h"

void main(void)
{
  WDTCTL=WDTPW+WDTHOLD;      //关闭看门狗
  BoardConfig();             //端口初始化
  Clock_Init();              //时钟初始化
  while(1)
  {
   unsigned int i;
   for(i=0;i<16;i++)
   {
    display1Digit(i, BIT7);  //轮次显示
    delay_ms(1000);          //延时1s
   }
  }
}

实验结果

点亮 LED 流水灯

独立按键控制 LED 灯亮灭

实现数码管显示数字 0~9 和字母 A~F

实验心得

本次实验主要目的是掌握 IO 端口的使用方法。在实验中我通过控制端口在需要的时候输出高、低电平来完成 LED 灯的亮灭变化,通过给数码管送译码完的数来完成数码管显示。

在第一个实验中,板子出现了灯没有按预期闪烁的问题,经调试后发现,应在 while 循环中每次左移后调用延时函数,使得 LED 灯的当前状态持续一段时间后再改变状态,才可以达到闪烁的效果。合理调用延时函数也可以消除按键抖动。

在第二个实验中,我是用了触发中断函数来配合按键按下,这样避免了系统轮询带来的额外功耗。

在第三个实验中,考虑到数码管显示是十分常用的模块,我将其封装为 display1Digitdisplay2Digits 函数,以便以后调用。在批量调试和调用相同模块的时候,模块化封装是很有必要的。

实验2:三人投票表决器

任务

三人投票表决器,按下按键进行表决,对应的 LED 灯点亮,数码管显示按下按键的个数。

流程图

程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include "msp430f149.h"
#include "config.h"
#include "uint.h"
#include "digit_disp.h"

unsigned char i0=0;         //记录第一个按键是否按过
unsigned char i1=0;         //记录第二个按键是否按过
unsigned char i2=0;         //记录第三个按键是否按过

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;
  BoardConfig();
  Clock_Init();
  P4DIR|=0xFF;
  P5DIR|=BIT5;
  P6DIR|=BIT6;
  P2DIR|=0xFF;
  P1DIR&=~(BIT0+BIT1+BIT2); //端口方向
  P1IES|=BIT0+BIT1+BIT2;    //边沿触发
  P1IFG&=~(BIT0+BIT1+BIT2); //清除中断标志
  P1IE|=BIT0+BIT1+BIT2;     //中断使能
  _BIS_SR(LPM3_bits+GIE);   //回归低功耗模式,全局中断
  while(1);
}

#pragma vector=PORT1_VECTOR
__interrupt void Port_1 (void)
{ 
  if(1)
  display1Digit((i0+i1+i2),BIT7);
  delay_ms(10);
  if(P1IFG&BIT0)         //检查第一个按键
  {
    if(i0==0)
    {
     P2OUT^=BIT0;        //翻转LED
     delay_ms(10);
     while(!(P1IN&BIT0));//检查按键释放
     P1IFG&=~BIT0;       //清除中断标志
     i0=1;
     display1Digit((i0+i1+i2),BIT7);
     delay_ms(10);
    }
    if(i0==1)
    {
     delay_ms(10);
     while(!(P1IN&BIT0));//检查按键释放
     P1IFG&=~BIT0;       //清除中断标志
     i0=1;
     display1Digit((i0+i1+i2),BIT7);
     delay_ms(10);
    }
  }
  if(P1IFG&BIT1)         //检查第二个按键
  { 
   if(i1==0)
   {
     P2OUT^=BIT1;
    delay_ms(10);
    while(!(P1IN&BIT1));
    P1IFG&=~BIT1;
    i1=1;
    display1Digit((i0+i1+i2),BIT7);
    delay_ms(10);
   }
   if(i1==1)
   {
    delay_ms(10);
    while(!(P1IN&BIT1));
    P1IFG&=~BIT1;
    i1=1;
    display1Digit((i0+i1+i2),BIT7);
    delay_ms(10);
   }
  }
  if(P1IFG&BIT2)         //检查第三个按键
  {
   if(i2==0)
   {
     P2OUT^=BIT2;
     delay_ms(10);
     while(!(P1IN&BIT2));
     P1IFG&=~BIT2;
     i2=1;
     display1Digit((i0+i1+i2),BIT7);
     delay_ms(10);
   }
   if(i2==1)
   {
    delay_ms(10);
    while(!(P1IN&BIT2));
    P1IFG&=~BIT2;
    i2=1;
    display1Digit((i0+i1+i2),BIT7);
    delay_ms(10);
   }
  }
}

实验结果

表决器正常运行

实验心得

在这次的实验中,我在表决器中引入了 i0i1i2 三个 flag 来记录按键状态,并在中断子程序中根据他们的值显示不同代码。画流程图(含中断)很有必要。有了流程图才能更方便精准地确定程序走向、初始化变量、构造子程序框架。

美中不足的是,这次程序在开发板上跑的并不是很理想,在多次频繁按下按键后程序显示不出对应的数值。

实验3:矩阵键盘显示

任务

初始状态下所有数码管都熄灭,分别按下矩阵键盘的按键 K1~K16,最右的两位数码管显示对应数值 01~16,其余数码管熄灭。

流程图

程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <msp430f149.h>
#include "config.h"
#include "uint.h"
#include "digit_disp.h"
#include "keyboard.h"

void timerInit(void);

void main(void)
{
  WDTCTL=WDTPW+WDTHOLD;                //关闭看门狗
  BoardConfig();                       //端口初始化
  Clock_Init();                        //时钟初始化
  timerInit(); 
  _EINT();                             //打开全局中断

  while(1)
  {
    keyboardScan();                    //低速:键盘扫描获取键值,送入缓冲区
  }
}

void timerInit(void)
{ 
    TACTL = TASSEL_1 + ID_3 + MC_1;    //定时器A的时钟源选择ACLK,增计数模式
    CCTL0 = CCIE;                      //使能CCR0中断
    CCR0 = 10;                         //设定刷新周期2mS       
}

#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A (void)
{ 
    display2Digits(Dispbuf,BIT7,BIT6); //高速:定时器控制刷新数码管,显示缓冲区数据
}

实验结果

键盘正常工作

实验心得

我认为这次实验是我完成的最优雅的一次实验,而低速读取键盘和高速刷新数码管是其中最优雅的部分。同时,在做到这个实验时,我开始封装部分经常使用的模块,这使得程序进一步形式精简,结构进一步优化。

实验4:人行交通灯控制器

任务

利用定时器 A 实现人行交通灯的控制。

  1. 初始状态红灯点亮;
  2. 按下按键后,保持红灯点亮的状态下,数码管从 10s 开始倒计时,当倒计时到 0 时,红灯熄灭, 绿灯点亮,行人可以通行;
  3. 在绿灯点亮的状态下,进行 15s 倒计时,倒计时到 3s 时,绿灯以 1s 为周期进行闪烁(每半秒改变一次状态),提醒行人绿灯即将结束,倒计时到 0 时,红灯点亮,数码管倒计时结束。

流程图

程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <msp430f149.h>
#include "config.h"
#include "uint.h"
#include "digit_disp.h"

int t=0;//t是程序总的执行时间

void main(void)
{
 WDTCTL = WDTPW+WDTHOLD;     //关闭看门狗
 BoardConfig();
 P4DIR=0xFF;                 //端口初始化
 P5DIR|=BIT5;
 P6DIR|=BIT6;
 P2DIR=0xFF;
 P2OUT&=~BIT0;
 P2OUT|=BIT3;                //P2.0红灯, P2.3绿灯
 TA0CCR0=32768/4;            //定时0.5s,因为是增减计数
 TA0CTL=TASSEL_1+MC_3+TACLR; //ACLK,增减计数模式,清除TAR
 TA0CCTL0=CCIE;              //打开定时器中断使能

 while(1)                    //按一次键执行一次完整的交通灯周期
 {
  if (!(P1IN&BIT0))          //判断P1.0按键是否按下
  { 
   t=0;
   TA0CTL|=TACLR;            //清除TAR
   while(!(P1IN&BIT0));      //按键是否按下
   __delay_cycles(100);      //延时消除抖动
   _BIS_SR(GIE);             //开启总中断

   while(t<=20)
   {
    display2Digits(10-t/2,BIT7,BIT6);
   }
   P2OUT|=BIT0;              // 红灯灭 
   P2OUT&=~BIT3;             // 绿灯亮

   while(t<=50)              //最后几秒手动控制亮灭
   {
    if(t==44)
    { 
      P2OUT|=BIT3;
    }
    if(t==45)
    {
      P2OUT&=~BIT3;
    }
    if(t==46)
    {
      P2OUT|=BIT3;
    }
    if(t==47)
    {
      P2OUT&=~BIT3;
    }
    if(t==48)
    { 
      P2OUT|=BIT3;
    }
    if(t==49)
    {
      P2OUT&=~BIT3;
    }
    display2Digits(25-(t/2),BIT7,BIT6);
   }
   P2OUT|=BIT3;              //绿灯灭
   P2OUT&=~BIT0;             //红灯亮
  }
 }
}

#pragma vector=TIMERA0_VECTOR
__interrupt void TIMERA0_ISR(void)
{
 t++;                        //每个定时周期自加
 TA0CTL&=~TAIFG;             //清除中断标志位
}

实验结果

交通灯计时准确,能按照设定逻辑工作

实验心得

本次实验难度较高,在拿到题目的时候,我很难一下子就有思路。主程序和中断怎么搭配、程序结构是什么样的、就着哪一条线索写程序,这些都让我感到困扰。但是真正写起来了就慢慢有了头绪,初改几次就能结束。这让我意识到,脑子里多想一想还不如手上多写一写。

实验5:简易数字电压表

任务

设计简易数字电压表,实现电压检测和显示功能。P6.0 引脚(A0 通道)采集模拟电压,利用 ADC12 模块 实现模数转换,1602 液晶屏显示电压测量结果。要求 ADC 采用内部 2.5V 基准电压,单通道多次转换模式。通过调节电位器,可以更改输入电压,测量结果最大显示值为 2.500V,精确到小数点后第三位。

流程图

程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "msp430f149.h"
#include "config.h"
#include "cry1602.h"
#include "cry1602.c"

unsigned char Number[]={"0123456789."};
unsigned char Title[]={"Volt:"};

void Trans_val(unsigned long Hex_Val)
{
  unsigned long Curr_Volt;                             //十进制电压值
  unsigned int i;                                      //电压值数组参数
  unsigned char Curr_Volt_Disp[5];                     //电压值显示数组
  
  Curr_Volt=Hex_Val*2500/4095;

  Curr_Volt_Disp[4]=Curr_Volt/1%10;
  Curr_Volt_Disp[3]=Curr_Volt/10%10;
  Curr_Volt_Disp[2]=Curr_Volt/100%10;
  Curr_Volt_Disp[0]=Curr_Volt/1000%10;
  Curr_Volt_Disp[1]=10;
  
  for(i=0;i<=4;i++)
    Disp1Char((5+i),0,Number[Curr_Volt_Disp[i]]);
}

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;
  Clock_Init();                                        //时钟初始化
  P1DIR=0xFF;P1OUT=0xFF;                               //端口初始化
  P2DIR=0xFF;P2OUT=0xFF;
  P3DIR=0xFF;P3OUT=0xFF;
  P4DIR=0xFF;P4OUT=0xFF;
  P5DIR=0xFF;P5OUT=0xFF;
  P6DIR=0xFF;P6OUT=0xFF;

  P6DIR|=BIT2;P6OUT|=BIT2;
  LcdReset();                                          //液晶初始化
  DispNChar(0,0,5,Title);                              //显示'Volt:'
  Disp1Char(10,0,'v');                                 //显示电压值
  P6SEL|=BIT0;                                         //采样通道
  ADC12CTL0=ADC12ON+REFON+REF2_5V+SHT0_2+MSC;         //打开ADC12_A,打开内部2.5V参考电压,周期16,多路采样转换
  ADC12CTL1=ADC12SSEL_0+SHP+CONSEQ_2+CSTARTADD_4;     //单通道重复转换,信号来自采样定时器,单通道重复,寄存器4
  ADC12MCTL4=SREF_1+INCH_0+EOS;                        //A4通道输入,VR+=VREF+,VR-=AVSS
  ADC12IE|=0x0010;                                     //4通道中断使能
  _EINT();
  ADC12CTL0|=ENC+ADC12SC;                              //转换使能,转换启动
}

#pragma vector=ADC_VECTOR
__interrupt void ADCISR(void)
{
  unsigned long result=ADC12MEM4;
  delay_ms(500);                                       //延时去抖动
  Trans_val(result);
}

实验结果

ADC转换灵敏,显示准确

实验心得

本次实验也有一定难度,我调试了好长时间却始终不行。在老师的帮助下,我对 ADC12MEM 寄存器输出格式有了进一步理解,并成功将其转化成十进制数值,逐位显示在液晶屏幕上;解决显示数据始终差约 32 倍的玄学问题后,我对中断和 ADC 转换模式的匹配也有了更深刻的理解。

实验6:UART 串口通信

任务

利用 MSP430 单片机和 PC 机进行 UART 串口通信,要求 430 单片机把从 PC 机接收到的数据再发送 给 PC 机,利用串口调试工具查看结果。

数据格式为 7 个数据位,1 个奇校验位,2 个停止位;波特率为 1200。

流程图

程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <msp430f149.h>
#include "config.h"

void main(void)
{
  WDTCTL=WDTPW+WDTHOLD;
  BoardConfig();
  Clock_Init();
  P3SEL|=BIT4+BIT5;        //P3.4和P3.5设为UART模式,选择串口收发功能
  P3DIR|=BIT4;             //设P3.4输出
  P3DIR&=~BIT5;            //设3.5输入  
  U0CTL|=SWRST;            //开始设置
  U0CTL=PENA+SPB;          //设置数据格式
  U0BR0=0x1B;
  U0BR1=0x00;              //设置波特率
  U0TCTL|=SSEL0;           //设置ACLK为时钟源(32768Hz)
  U0CTL&=~SWRST;           //结束设置
  ME1|=URXE0+UTXE0;
  IE1|=URXIE0;
  _EINT();                 //开全局中断
}

#pragma vector=UART0RX_VECTOR
__interrupt void UART0_RX(void)
{
  while(!(IFG1&UTXIFG0));  //如果已经发送完成
  TXBUF0=RXBUF0;           //将接受的送入发送端
}

实验结果

UART传输响应迅速,功能正常

实验心得

本次实验难度不高,但是仍有细节需要注意。例如 U0CTL|=SWRST;U0CTL&=~SWRST; 这一对指令不能忘记,在最后测试的时候需要接上额外的 232 串口线而不是用 usb 端口。这些细节都是影响实验结果的关键。

课程设计总结

首先我十分感谢老师和同学的帮助,是他们的帮助让我巩固了书本知识,锻炼了编程能力,掌握了调试技巧,最终顺利完成所有实验。

本课程设计的内容的每一部分,我都踏踏实实地完成,因此我也有很多心得感想。

在 IO 接口实验中,我熟悉了 IAR、MSPFET 的使用,了解了单片机程序设计到实现的基本过程。尽管实验内容本身很简单,但是在接触复杂实验之前通过一次简单的实验来掌握框架是十分重要的。在理论学习的时候我就对中断有很多疑问甚至十分畏惧,因此我通过1.2实验重温了中断原理和触发机制,也巩固了多源中断和标志位清零的相关内容。

在投票器实验中,我用顺序结构完成了所要求的内容,尽管这个投票器只能运行一轮……显然它不能称得上是一份“优雅”的设计,因为完备性始终是需要考虑的一环,虽然它并不在实验要求之内,但这对于我自己来说是个遗憾。

矩阵键盘是我做的最兴奋的实验。一开始我使用静态显示和主函数内动态显示,都不能很好地完成两位数的同时显示。同时,身边的同学和我都出现了数码管余晖过于明显的问题。结合我自己对于 LCD 背光显示的一些基本了解,我认为这是数码管刷新率太低导致的。我想到接口里面学到的高速低速分开处理的策略,将这个思路迁移到实验中,把获取键盘这一低速任务放到主函数,把数码管实时刷新这个高速任务放进中断,同时设置一个计时器高频率触发中断,以达到高低速进程分开的目的。

当老师考核的时候抽到我这个实验,我真是为这个思路而感到兴奋。这让我对计时器中断有了进一步认识:计时器中断可以产生不同形状可频率的波形,也可以利用这些波形来选择性地触发进程,从而达到高低速分开的优化效果。

也是在矩阵键盘的实验中,我尝试封装了常用的数码管显示函数和它们需要的 array[ ] 译码数组。我认为模块化和可移植性是衡量一份代码是否“优雅”最基础的指标。假如一份代码所有的功能全都写在一个文件中,或是写在一个主函数里面,变量作用域模糊、函数内容耦合严重等问题就会接踵而至。我力求以“说人话”的方式封装了几个函数,也尝试在其他同学的主程序中调用,均能实现功能。

交通灯的实验我一开始并没有什么思路,正如我在对应实验的心得体会中所言,在拿到题目的时候,我很难一下子就有思路。主程序和中断怎么搭配、程序结构是什么样的、就着哪一条线索写程序,这些都让我感到困扰。但是真正写起来了就慢慢有了头绪,初改几次就能结束。这让我意识到,脑子里多想一想还不如手上多写一写,如果不知道怎么写就先用简单的顺序结构,写一遍出来再慢慢调整,总会有柳暗花明的时候。

ADC电压表实验也同样令我难忘。本来简简单单程序跑出来的结果始终非常小,但是在我发现调整 Curr_Volt=Hex_Val*2500/4095; 的分子可以比例扩大输出值后,我调出了一个让大家啼笑皆非的分子 77558。老师也帮我 debug 好长时间,一开始注意点都在变量类型上,后来才发现是我自己一个累计 32 次后取均值输出一次的去抖动的“微操”和中断触发不匹配,导致结果差了约 32 倍。

这次实验加深了我对于寄存器输出值的理解。我也认识到,主进程和触发中断的模式应该要匹配,这样才能发挥作用。

在想不出什么好办法后,也要敢于厚着脸皮试试看,能不能通过“土办法”解决问题。这并不是一件令人羞耻的事情。

那天晚上我准备去吃饭,在电梯口遇到了刚刚下楼,却想到问题马上又上来的老师。我们不约而同认为是变量类型的问题(尽管并不是),又一起 debug 了好一会儿。

我十分感动,不仅仅是因为老师把我的实验 bug 放在心上,也是因为我感受到了在硬件爱好者群体中(在学校同学群体中十分少见的)才有的那种不解决完 bug 不罢休的激情。

UART 实验到十分简单,并没有什么特别的感想。

我在撰写这份实验报告的时候,想到那令我满意的矩阵键盘,心中不免再次激动。但是几天前,我在网上偶然接触到一些电脑外设的设计,其中行列扫描、并入串出、高低速分离、冲突处理等等,原来都是别人早就玩烂了的成熟功能,而我还在为一个矩阵键盘的“小聪明”而洋洋得意。这是何等的鼠目寸光。

不过这也正常,有谁不会为自己一个巧妙的设计而兴奋呢?如果我下次想到一种巧妙的设计,不会感到它十分“优雅”,而是感到十分“基操”,那也许就意味着我掌握的技术体系再一次进步了。

最后,我再次感谢老师同学的帮助。这真是一次难忘的课设!

附件

uint.h

1
2
3
4
//------------------------------------------------------------------预定义
#define uint8                   unsigned char                     //8位数
#define uint16                  unsigned int                     //16位数
//-----------------------------------------------------------------------

digit_disp.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//------------------------------------------------------------------预定义
#define digitSelectionOn        P5OUT|=BIT5               //数码管位选开启
#define digitSelectionOff       P5OUT&=~BIT5              //数码管位选关闭
#define segmentSelectionOn      P6OUT|=BIT6               //数码管段选开启
#define segmentSelectionOff     P6OUT&=~BIT6              //数码管段选关闭
//-----------------------------------------------------------------------



//-------------------------------------------------------------全局变量区
//<变 量 类 型>        <变 量 名>           <含 义>           <被使用函数>
//  uint8 []            array           数码管显示译码表     display1Digit
//                                                         display2Digits
//-----------------------------------------------------------------------



//--------------------------------------------------------------总函数声明
void display1Digit(uint8 num, uint8 location);
void display2Digits(uint16 num,uint8 location0,uint8 location1);
//-----------------------------------------------------------------------



/***********************************************
函数名称:display1Digit、display2Digits
功    能:数码管动态显示(1位和2位)
参    数:num--待显示数字 
         location、location0、location1--显示位置   
            可选BIT0~BIT7
            对应八只数码管最左边到最右边
返回值  :无
***********************************************/
uint8 array[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};

void display1Digit(uint8 num, uint8 location)
{
    segmentSelectionOff;     //关闭段选
    P4OUT=~location;         //选择位选
    digitSelectionOn;        //打开位选
    digitSelectionOff;       //关闭位选
    P4OUT=array[num];        //段选输出相应数码
    segmentSelectionOn;      //打开段选   
}
void display2Digits(uint16 num,uint8 location0,uint8 location1)
{
    display1Digit(num%10,location0);     //个位数
    delay_us(1000);
    display1Digit(num/10,location1);     //十位数
    delay_us(1000);
}

keyboard.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//------------------------------------------------------------------预定义
#define keyin_x              (P1IN & 0x0f)        //键盘函数行坐标扫描运算
//-----------------------------------------------------------------------



//-------------------------------------------------------------全局变量区
//<变 量 类 型>        <变 量 名>           <含 义>           <被使用函数>
//   uint8             Dispbuf          键盘缓冲区变量       keyboardScan
//-----------------------------------------------------------------------



//--------------------------------------------------------------总函数声明
void delay(void);
uint8 keyboardScan(void);
//-----------------------------------------------------------------------



/***********************************************
函数名称:delay(也可用config.h里的delay_us)
功    能:延时消抖
参    数:无
返回值  :无
***********************************************/
void delay(void)
{
  uint16 tmp;
  for(tmp = 12000;tmp > 0;tmp--);
}
/***********************************************
函数名称:keyboardScan
功    能:键盘扫描,存入缓冲区
参    数:无
返回值  :Dispbuf
***********************************************/
uint8 Dispbuf=0;          //缓冲区全局变量

uint8 keyboardScan(void)  //二级定位,先定列坐标,再行扫描
{
  uint8 temp;
  P1DIR=0XF0;             //P1.0~P1.3(PA10~PA13)输入(行坐标)
  P1OUT=0X0F;             //P1.4~P1.7(PA14~PA17)输出清零

  if(keyin_x != 0x0f)     //若有键被按下
  {
    delay();              //延时消抖
    if(keyin_x != 0x0f)   //再次检测按键状态
    {
      P1OUT = 0XE0;       //P1.4置零,固定按键列坐标,即只扫描第一行;此时观察P1.0~P1.3哪一位为0就能确定按键的行坐标
      temp = keyin_x;     //取低四位
      switch(temp)        //转换键值
        {
          case 0x0e: Dispbuf =  1; break;
          case 0x0d: Dispbuf =  2; break;
          case 0x0b: Dispbuf =  3; break;
          case 0x07: Dispbuf =  4; break;
        }

      P1OUT = 0XD0;       //P1.5置零,同理
      temp = keyin_x;     //取低四位
      switch(temp)        //转换键值
        {
          case 0x0e: Dispbuf =  5; break;
          case 0x0d: Dispbuf =  6; break;
          case 0x0b: Dispbuf =  7; break;
          case 0x07: Dispbuf =  8; break;
        }

      P1OUT = 0XB0;       //P1.6置零,同理
      temp = keyin_x;     //取低四位
      switch(temp)        //转换键值
        {
          case 0x0e: Dispbuf =  9; break;
          case 0x0d: Dispbuf = 10; break;
          case 0x0b: Dispbuf = 11; break;
          case 0x07: Dispbuf = 12; break;
        }

      P1OUT = 0X70;       //P1.5置零,同理
      temp = keyin_x;     //取低四位
      switch(temp)        //转换键值
        {
          case 0x0e: Dispbuf = 13; break;
          case 0x0d: Dispbuf = 14; break;
          case 0x0b: Dispbuf = 15; break;
          case 0x07: Dispbuf = 16; break;
        }
      }
  }
  return Dispbuf;
}
Built with Hugo
主题 StackJimmy 设计
# /layouts/partials/footer/custom.html