CVAVR生成的典型USART收发的接口程序(MEGA16只有一个串口所以不用区分是哪个串口的中断相量编号,而MEGA2560多个串口,在头文件中就区分开多个串口的中断相量编号,参考各自头文件)

 一般教科书上提供的UART收发的程序往往是一段采用轮循(Polling)方式完成收发的简单代码。但对于高速的AVR来讲,采用这种方式大大降低了MUC的效率。在使用AVR时,应根据芯片本身的特点(片内大容量数据存储器RAM,更适合采用高级语言编写系统程序),编写高效可靠的UART收发接口(低层)程序。下面是一个典型的USART的接口程序。(下面是CodeVisionAVR修改成WINAVR后的程序,原来的程序请看底下给出的链界,在http://www.ouravr.com/的论坛里)

/*****************************************************

 // usart.h

 // 常量定义
 #define  BAUDRATE        9600     // 波特率
 // #define F_CPU            4000000   // 晶振频率4.0MHz

 #define  RXB8 1
 #define  TXB8 0
 #define  PE 2     // M16
 // #define UPE 2     // M128
 #define  OVR 3
 #define  FE 4
 #define  UDRE 5
 #define  RXC 7

 // 宏定义
 #define  FRAMING_ERROR (1<<FE)
 #define  PARITY_ERROR (1<<PE)     // M16
 // #define PARITY_ERROR (1<<UPE)     // M128
 #define  DATA_OVERRUN (1<<OVR)
 #define  DATA_REGISTER_EMPTY (1<<UDRE)
 #define  RX_COMPLETE (1<<RXC)

 //  USART Receiver buffer
 //  全局变量,会在中断服务程序中被修改,须加volatile限定,不要就会出错啦
 #define  RX_BUFFER_SIZE 16          //  接收缓冲区大小,可根据需要修改
 volatile   char  rx_buffer[RX_BUFFER_SIZE];  //  接收缓冲区,为char型变量组成的数组,该数组构成环形队列,个数为RX_BUFFER_SIZE 
 volatile  unsigned  char  rx_wr_index,rx_rd_index,rx_counter;
 //  This flag is set on USART Receiver buffer overflow
 volatile   char  rx_buffer_overflow;    // 接收缓冲区溢出标志

 //  USART Transmitter buffer
 #define  TX_BUFFER_SIZE 16
 volatile   char  tx_buffer[TX_BUFFER_SIZE];
 volatile  unsigned  char  tx_wr_index,tx_rd_index,tx_counter;

 //  函数声明
 char  get_c( void );
 void  put_c( char  c);
 void  put_s( char   * ptr);
 void  init_USART( void );
 

 // usart.c

#include  < avr / io.h >
#include  < stdio.h >
#include  < avr / interrupt.h >
#include  " usart.h "

 /*接收中断*/
ISR(USART_RXC_vect)
 {
    char status,data;
    status=UCSRA;     //读取接收状态标志位,必须先读,当读了UDR后,UCSRA便自动清零了  
    data=UDR;         //读取USART数据寄存器,这句与上句位置不能颠倒的
    if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)     //判断本接收到的数据是否有数据帧、校验或数据溢出错误(此处指USART的硬件接收溢出)
       {
           rx_buffer[rx_wr_index]=data;   // 将数据填充到接收缓冲队列中
           if (++rx_wr_index == RX_BUFFER_SIZE)    //写指针指向下一个单元,并判断是否到了队列的尾部,(不表示接受缓冲区是否满!)
               rx_wr_index=0;  //到了尾部,则指向头部(构成环状)
           if (++rx_counter == RX_BUFFER_SIZE)     //队列中收到字符加1,并判断是否队列已满 
          {
              rx_counter=0;   // 队列满了,队列中收到字符个数为0,表示队列中所有以前的数据作废,因为最后的数据已经把最前边的数据覆盖了
              rx_buffer_overflow=1;        //置缓冲区溢出标志。在主程序中必要的地方需要判断该标志,以证明读到数据的完整性
          };
       };
}
 /*接收单个字符*/
 char  get_c( void )
 {
    char data;
    while (rx_counter==0);             //接收数据队列中没有数据可以读取,等待......(注2)
    data=rx_buffer[rx_rd_index];       //读取缓冲队列中的数据
    if (++rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0;     //读取指针指向下一个未读的数据,如果指到了队列尾部,则指回到队列头步
    cli();    // 关中断!非常重要
    --rx_counter;  //队列中未读数据个数减1。因为该变量在接收中断中要改变的,为了防止冲突,所以改动前临时关闭中断。程序相当可靠了。
    sei();    // 开中断
    return data;
}

 // 发送中断
 ISR(USART_TXC_vect)
 {
    if (tx_counter)
    {
           --tx_counter;
           UDR=tx_buffer[tx_rd_index];
           if (++tx_rd_index == TX_BUFFER_SIZE) tx_rd_index=0;
    }; 
}
 /*发送单个字符*/
 void  put_c( char  c)
 {
    while (tx_counter == TX_BUFFER_SIZE);   //发送数据队列中还有数据没有发送完,等待
    cli();
    if (tx_counter || ((UCSRA & DATA_REGISTER_EMPTY)==0))    //若发送数据队列有数据或者数据寄存器UDR非空时执行(因为队列先进先出的原因,所以,c要放进非空的发送数据队列里面)
       {
           tx_buffer[tx_wr_index]=c;
           if (++tx_wr_index == TX_BUFFER_SIZE) tx_wr_index=0;
           ++tx_counter;
       }
    else
           UDR=c;
    sei();
}
 /*发送字符串*/
 void  put_s( char   * ptr)
 {
    while (*ptr)
    {
        put_c(*ptr++);
    }
    put_c(0x0D);
    put_c(0x0A);  //结尾发送回车换行
}

 /*USART 初始化*/
 void  init_USART( void )
 {

    //USART 9600 8, n,1  PC上位机软件(超级终端等)也要设成同样的设置才能通讯
    UCSRC = (1<<URSEL) | 0x06;
    UBRRL= (F_CPU/BAUDRATE/16-1)%256;
    UBRRH= (F_CPU/BAUDRATE/16-1)/256;
    UCSRA = 0x00;
    //接收使能,发送使能,接收中断使能,发送中断使能
    UCSRB=(1<<RXCIE)|(1<<TXCIE)|(1<<RXEN)|(1<<TXEN);
}

 /***********************************************
**** 名  称:AVR USART(RS232)低层驱动+中间层软件示例        
****                                          
**** 作  者:zhiyu                       
**** 编译器:WINAVR20070525                   
****                                         
**** 参  考:http://www.ouravr.com/bbs/bbs_content.jsp?mother_form=bbs_content.jsp&bbs_id=1000&bbs_page_no=1&bbs_sn=147242
             《高档8位单片机ATmega128原理与开发应用指南(上)》--马潮  P320
             《嵌入式C编程与ATMEL AVR》-- 国外计算机经典教材 P141
**** 日  期:2007.07.19
****
**** 芯  片:M16L
**** 时钟源:外部4M晶振
****
**** 结  果:测试成功
**** 问  题:暂无
***********************************************/
 // #include <avr/io.h>
 // #include <stdio.h>
 #include  < avr / interrupt.h >
#include  " usart.h "

 int  main( void )
 {
    init_USART();
    sei();    //总中断允许
    
    put_s("Hello!");
    put_s("这是一个简单的高速的串口驱动程序");
    put_s("请你输入任意的字符,单片机将返回你输入的字符");
    while (1)
    {
        put_c(get_c());
    }
}
 

 // Makefile,主要的几项,只是针对我这里的程序,要灵活运用哦

MCU  =  atmega16

F_CPU  =   4000000

TARGET  =  main

SRC  =  $(TARGET).c usart.c    // 多文件编译才会用到这一项,可以参考这个帖子:

http: // www.mcublog.com/blog/user1/4266/archives/2006/6145.html

*****************************************************/

 这段由CVAVR程序生成器产生的UART接口代码是一个非常好的、高效可靠,并且值得认真学习和体会的。其特点如下:
   l.它采用两个8字节的接收和发送缓冲器来提高MCU的效率.当主程序调用getchar()函数时,按顺序执行到while (rx_counter==0)处,接收数据队列里面就没有数据,如果再没有数据输入,那么就只能死在那里等待.如果有数据输入的话,中断很快就响应,数据就会迅速地填充接收数据队列,rx_counter!=0,这个死等待也就给瓦解了,让程序执行接下来的那句data=rx_buffer[rx_rd_index]了.最后return data;,返回输入的值;如当主程序调用Putchar()发送数据时,如果UART口不空闲,就将数据放入发送缓冲器中,MCU不必等待,可以继续执行其它的工作。而UART的硬件发送完一个数据后,产生中断,由中断服务程序负责将发送缓冲器中数据依次自动送出。

C语言书本里有其中一段:

      getchar()函数(字符输入函数)的作用是从终端(或系统隐含指定的输入设备)输入一个字符.getchar()函数没有参数.当你输入一个字符时候,比如'a'后,要按'Enter'键,字符才能送到内存!你一旦按了这个'Enter',上面的程序就会执行中断响应了,
   2.数据缓冲器结构是一个线性的循环队列,由读、写和队列计数器3个指针控制,用于判断队列是否空、溢出,以及当前数据在队列中的位置。
   3.用编译控制命令#pragma savereg-和#pragma savereg+,使得由CVAVR在生成的中断服务程序中不进行中断保护(CVAVR生成中断保护会将比较多的寄存器压入堆栈中),而在中断中嵌入汇编,只将5个在本中断中必须要保护的寄存器压栈。这样提高了UART中断处理的速度,也意味着提高了MCU的效率。
   4.由于在接口程序Putchar()、Getchar()和中断服务程序中都要对数据缓冲器的读、写和队列计数器3个指针判断和操作,为了防止冲突,在Putchar()、Getchar()中对3个指针操作时临时将中断关闭,提高了程序的可靠性。
    建议读者能逐字逐句地仔细分析该段代码,真正理解和领会每一句语句(包括编译控制命令的作用)的作用,从中体会和学习如何编写效率高,可靠性好,结构优良的系统代码。这段程序使用的方法和技巧,对编写SPI、I2C的串行通信接口程序都是非常好的借鉴。
    作为现在的单片机和嵌入式系统的工程师,不仅要深入全面的掌握芯片和各种器件的性能,具备丰富的硬件设计能力;同时也必须提高软件的设计能力。要学习和掌握有关数据结构、操作系统、软件工程、网络协议等方面的知识,具有设计编写大的复杂系统程序的能力。

/*=================================================

链接: http://www.ouravr.com/bbs/bbs_content.jsp?bbs_sn=147242&bbs_page_no=1&sub_kind_id=1430&bbs_id=1000

http://www2.ouravr.com/bbs/bbs_content.jsp? mother_form=bbs_content.jsp&amp;bbs_id=1000&amp;bbs_page_no=1&amp;bbs_sn=528742

http://bbs.avrvi.com/simple/index.php?t3193.html

书籍: 高档8位单片机ATmega128原理与开发应用指南(上) P320

         嵌入式C编程与Atmel AVR P141 第三章 标准I/O和预处理函数 (网上有这本电子书的下载)

下面是马潮老师的说法:

在CVAVR系统提供的标准库函数stdio.h中,提供了getchar()函数,该函数是采用轮询方式从USART接收数据的,轮询方式不仅效率低,而且会丢失数据,不能实现多任务的并行处理。

CVAVR程序向导中给出的采用中断+缓冲的方式接受数据,同PC的串口接收数据的方法一样,充分利用了AVR的高速和RAM多的优点,体现出了如何才能充分发挥AVR的特点的程序设计思想,这种思路在32位系统中也是这样的。

使用AVR的话,对软件的设计能力要求更高了,否则根本不能发挥和体现AVR的特点。许多人有了一点C的基础,就认为采用C编写单片机程序没问题,很快就会掌握AVR了,对此我只能一笑了之。看看本站上众多的代码,再看看本贴的遭遇,能说什么呢?

还有,你可以参考一下这里的链接:http://www.iccavr.com/forum/dispbbs.asp?boardID=2&ID=2249&page=1这人朋友些得不错,主要是因为:

 #define  RX_BUFFER_SIZE0 8  // 收件箱的长度

unsigned  char  rx_buffer0[RX_BUFFER_SIZE0];    // 收信箱
 unsigned  char  rx_wr_index0;    // 收信箱写指针
 unsigned  char  rx_rd_index0;    // 收信箱读指针
 unsigned  char  rx_counter0;     // 收信箱存量
 unsigned  char  rx_buffer_overflow0;   // 收信箱满标志位

这样的比喻不是很好理解了吗,你要是脑子发散一点,会不会想到操作系统里面的"管道"的概念呢,其实现在我也没具体去看这东西,不过也会有些相通的地方吧.

回到本题:
注1:
如果在程序的开头部分加上语句
#define _DEBUG_TERMINAL_IO_
那么程序在编译时仍使用系统自己的getchar()函数,这样在软件模拟仿真时,可以从模拟的终端读取数据,便于在软件模拟环境中调试整个系统,而需要正式运行时,则把该句注释掉。
注2:
此处在正式应用中应根据实际情况做适当的修改。否则当主程序调用getchar()时,如果缓冲队列中没有数据,同时对方也没有发数据的情况时,程序会在此死循环。
比较简单的办法是将这句删掉,而在调用getchar()函数前先判断rx_counter的值,为0的话就不调用了。
或改为:

   signed  int  getchar( void )  
   {  
    signed int data;  
    if (rx_counter == 0) 
    { 
        data = -1;     
    } 
    else 
    { 
        data=rx_buffer[rx_rd_index];   //读取缓冲队列中的数据  
        if (++rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0;  //读取指针指向下一个未读的数据,如果指到了队列尾部,则指回到队列头步  
        #asm("cli")      // 关中断!非常重要                                               
        --rx_counter;    //队列中未读数据个数减1。因为该变量在接收中断中要改变的,为了防止冲突,所以改动前临时关闭中断。程序相当可靠了。  
        #asm("sei")      // 开中断 
    } 
    return data;  
}  
 

注3:
有兴趣希望深入实在学习的网友,可将CVAVR生成的USART发送代码仔细分析以下。它的发送代码非常完美,可以马上使用。
思考分析:

 #include  < mega16.h >  

 #define  RXB8 1 
 #define  TXB8 0 
 #define  UPE 2 
 #define  OVR 3 
 #define  FE 4 
 #define  UDRE 5 
 #define  RXC 7 

 #define  FRAMING_ERROR (1<<FE) 
 #define  PARITY_ERROR (1<<UPE) 
 #define  DATA_OVERRUN (1<<OVR) 
 #define  DATA_REGISTER_EMPTY (1<<UDRE) 
 #define  RX_COMPLETE (1<<RXC) 

 //  USART Transmitter buffer 
 #define  TX_BUFFER_SIZE 8 
 char  tx_buffer[TX_BUFFER_SIZE]; 

 #if  TX_BUFFER_SIZE<256 
unsigned  char  tx_wr_index,tx_rd_index,tx_counter; 
 #else  
unsigned  int  tx_wr_index,tx_rd_index,tx_counter; 
 #endif  

 //  USART Transmitter interrupt service routine 
 interrupt [USART_TXC]  void  usart_tx_isr( void ) 
 { 
if (tx_counter) 
   { 
   --tx_counter; 
   UDR=tx_buffer[tx_rd_index]; 
   if (++tx_rd_index == TX_BUFFER_SIZE) tx_rd_index=0; 
   }; 
}  

#ifndef _DEBUG_TERMINAL_IO_ 
 //  Write a character to the USART Transmitter buffer 
 #define  _ALTERNATE_PUTCHAR_ 
 #pragma  used+ 
 void  putchar( char  c) 
 { 
while (tx_counter == TX_BUFFER_SIZE); 
#asm("cli") 
if (tx_counter || ((UCSRA & DATA_REGISTER_EMPTY)==0)) 
   { 
   tx_buffer[tx_wr_index]=c; 
   if (++tx_wr_index == TX_BUFFER_SIZE) tx_wr_index=0; 
   ++tx_counter; 
   } 
else 
   UDR=c; 
#asm("sei") 
}  
 #pragma  used- 
 #endif  

 //  Standard Input/Output functions 
 #include  < stdio.h >  

 //  Declare your global variables here 

 void  main( void ) 
 { 
// Declare your local variables here 

// Input/Output Ports initialization 
// Port A initialization 
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In  
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T  
PORTA=0x00; 
DDRA=0x00; 

// Port B initialization 
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In  
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T  
PORTB=0x00; 
DDRB=0x00; 

// Port C initialization 
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In  
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T  
PORTC=0x00; 
DDRC=0x00; 

// Port D initialization 
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In  
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T  
PORTD=0x00; 
DDRD=0x00; 

// Timer/Counter 0 initialization 
// Clock source: System Clock 
// Clock value: Timer 0 Stopped 
// Mode: Normal top=FFh 
// OC0 output: Disconnected 
TCCR0=0x00; 
TCNT0=0x00; 
OCR0=0x00; 

// Timer/Counter 1 initialization 
// Clock source: System Clock 
// Clock value: Timer 1 Stopped 
// Mode: Normal top=FFFFh 
// OC1A output: Discon. 
// OC1B output: Discon. 
// Noise Canceler: Off 
// Input Capture on Falling Edge 
// Timer 1 Overflow Interrupt: Off 
// Input Capture Interrupt: Off 
// Compare A Match Interrupt: Off 
// Compare B Match Interrupt: Off 
TCCR1A=0x00; 
TCCR1B=0x00; 
TCNT1H=0x00; 
TCNT1L=0x00; 
ICR1H=0x00; 
ICR1L=0x00; 
OCR1AH=0x00; 
OCR1AL=0x00; 
OCR1BH=0x00; 
OCR1BL=0x00; 

// Timer/Counter 2 initialization 
// Clock source: System Clock 
// Clock value: Timer 2 Stopped 
// Mode: Normal top=FFh 
// OC2 output: Disconnected 
ASSR=0x00; 
TCCR2=0x00; 
TCNT2=0x00; 
OCR2=0x00; 

// External Interrupt(s) initialization 
// INT0: Off 
// INT1: Off 
// INT2: Off 
MCUCR=0x00; 
MCUCSR=0x00; 

// Timer(s)/Counter(s) Interrupt(s) initialization 
TIMSK=0x00; 

// USART initialization 
// Communication Parameters: 8 Data, 1 Stop, No Parity 
// USART Receiver: Off 
// USART Transmitter: On 
// USART Mode: Asynchronous 
// USART Baud rate: 9600 
UCSRA=0x00; 
UCSRB=0x48; 
UCSRC=0x86; 
UBRRH=0x00; 
UBRRL=0x19;   //波特率设置有疑问待议  2021.9.30

// Analog Comparator initialization 
// Analog Comparator: Off 
// Analog Comparator Input Capture by Timer/Counter 1: Off 
ACSR=0x80; 
SFIOR=0x00; 

// Global enable interrupts 
#asm("sei") 

while (1) 
      { 
      // Place your code here 
      putchar(0x55); 
      }; 
}  

我在主程序的循环里仅有一句不停的发0X55,问题是AVR的运行速度非常快,而USART串出的速度肯定明显的慢(按9600bps计算,需要1秒多时间才能送出1000个字符),那么,假定主程序循环了1000次,发送1000个0x55,请判断在UASRT口上能否正确的发出1000个0x55,有没有丢失或溢出现象存在?

答:不会。因为putchar()中有这句:

while (tx_counter == TX_BUFFER_SIZE);

表示如果缓冲满就等待
======================================================*/
————————————————
版权声明:本文为CSDN博主「zhiyu520」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhiyu520/article/details/1795332

MEGA2560头文件中断相量定义

// Interrupt vectors definitions

#define EXT_INT0 2
#define EXT_INT1 3
#define EXT_INT2 4
#define EXT_INT3 5
#define EXT_INT4 6
#define EXT_INT5 7
#define EXT_INT6 8
#define EXT_INT7 9
#define PC_INT0 10
#define PC_INT1 11
#define PC_INT2 12
#define WDT 13
#define TIM2_COMPA 14
#define TIM2_COMPB 15
#define TIM2_OVF 16
#define TIM1_CAPT 17
#define TIM1_COMPA 18
#define TIM1_COMPB 19
#define TIM1_COMPC 20
#define TIM1_OVF 21
#define TIM0_COMPA 22
#define TIM0_COMPB 23
#define TIM0_OVF 24
#define SPI_STC 25
#define USART0_RXC 26
#define USART0_UDRE 27
#define USART0_TXC 28
#define ANA_COMP 29
#define ADC_INT 30
#define EE_RDY 31
#define TIM3_CAPT 32
#define TIM3_COMPA 33
#define TIM3_COMPB 34
#define TIM3_COMPC 35
#define TIM3_OVF 36
#define USART1_RXC 37
#define USART1_UDRE 38
#define USART1_TXC 39
#define TWI 40
#define SPM_READY 41
#define TIM4_CAPT 42
#define TIM4_COMPA 43
#define TIM4_COMPB 44
#define TIM4_COMPC 45
#define TIM4_OVF 46
#define TIM5_CAPT 47
#define TIM5_COMPA 48
#define TIM5_COMPB 49
#define TIM5_COMPC 50
#define TIM5_OVF 51
#define USART2_RXC 52
#define USART2_UDRE 53
#define USART2_TXC 54
#define USART3_RXC 55
#define USART3_UDRE 56
#define USART3_TXC 57

#define USART_RXC USART0_RXC
#define USART_DRE USART0_UDRE
#define USART_TXC USART0_TXC

最后三个宏定义相当于把USART_RXC等三定义默认为串口0的宏定义

MEGA16头文件中中断相量定义

// Interrupt vectors definitions

#define EXT_INT0 2
#define EXT_INT1 3
#define TIM2_COMP 4
#define TIM2_OVF 5
#define TIM1_CAPT 6
#define TIM1_COMPA 7
#define TIM1_COMPB 8
#define TIM1_OVF 9
#define TIM0_OVF 10
#define SPI_STC 11
#define USART_RXC 12
#define USART_DRE 13
#define USART_TXC 14
#define ADC_INT 15
#define EE_RDY 16
#define ANA_COMP 17
#define TWI 18
#define EXT_INT2 19
#define TIM0_COMP 20
#define SPM_READY 21
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个很有趣的题目!首先我们需要准备一些硬件组件,包括: 1. 一个时钟晶振模块,用于提供准确的时间基准信号。 2. 一个 LCD 模块,用于显示时钟信息。 3. 若干个按键,用于调整时钟。 然后我们可以始编写程序了。这里我以 ATmega16 为例,使用 C 语言编写。 1. 配置时钟模块,使其输出 1 秒中断信号。 ```c void init_timer() { // 设置定时器1的时钟源为外部晶振,频率为 1MHz TCCR1B |= (1 << CS12) | (1 << CS10); // 设置定时器1的计数上限为 1000,即 1s OCR1A = 1000; // 允许定时器1比较匹配中断 TIMSK |= (1 << OCIE1A); } ``` 2. 实现中断函数,每 1 秒更新时钟信息并显示在 LCD 上。 ```c ISR(TIMER1_COMPA_vect) { // 更新时钟信息 seconds++; if (seconds >= 60) { seconds = 0; minutes++; if (minutes >= 60) { minutes = 0; hours++; if (hours >= 24) hours = 0; } } // 显示时钟信息 char buffer[17]; sprintf(buffer, "%02d:%02d:%02d", hours, minutes, seconds); lcd_write_string(buffer); } ``` 3. 实现按键中断函数,根据按键类型调整时钟信息。 ```c ISR(INT0_vect) { // 调时 hours++; if (hours >= 24) hours = 0; } ISR(INT1_vect) { // 调分 minutes++; if (minutes >= 60) minutes = 0; } ISR(INT2_vect) { // 调秒 seconds++; if (seconds >= 60) seconds = 0; } ``` 4. 配置按键中断,使其在按键被按下时触发中断。 ```c void init_interrupts() { // 配置 INT0, INT1, INT2 为下降沿触发中断 EICRA |= (1 << ISC01) | (1 << ISC11) | (1 << ISC21); // 允许 INT0, INT1, INT2 中断 EIMSK |= (1 << INT0) | (1 << INT1) | (1 << INT2); } ``` 最后,我们只需要在 `main` 函数调用上述函数即可。 ```c int main() { // 初始化硬件 lcd_init(); init_timer(); init_interrupts(); // 启用全局中断 sei(); // 主循环,不需要做任何事情 while (1) {} return 0; } ``` 这就是一个简单的时钟系统的实现方法。当然,还有很多细节需要注意,比如按键 debounce、LCD 显示优化等等,但基本的框架就是这样了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值