AVR C语言编程的小技巧-IO口置位

给单片机IO口置位是编程用的比较多的操作,这是我在学习C语言编程中的一点小小心得,希望大家觉得有用!

AVR 单片机的IO口是标准的双向端口,首先要设置IO口的状态,即:输入还是输出

DDRx寄存器就是AVR单片机的端口方向寄存器,通过设置DDRx可以设置x端口的状态。
DDRx端口方向寄存器相应位设置为1则对应的x端口相应位为输出状态,DDRx端口方向寄存器相应位设置为0则对应的x端口相应位为输入状态。
例如:
DDRA = 0xFF;  //设置端口A所有口为输出状态,因为0xFF对应的二进制为11111111b

DDRA = 0x0F  //设置端口A高4位为输入状态,低4位为输出状态,因为0x0F对应的二进制为00001111b


PORTx寄存器是AVR单片机的输出寄存器,端口输出状态设定好后通过设置PORTx可以使端口x的相应位输入高电平或低电平来控制外部设备。
例如:
PORTA = 0xFF;  //端口A所有口线输出高电平

PORTA = 0x0F;  //端口A高4位输出低电平,低4位输出高电平

小贴士:
利用位逻辑运算符对特定的端口进行设定。

PORTA = 1<<3;  //端口A第4位置为高电平,其它为低电平,应为00000001左移3位后是00001000
PORTA = 1<<7;  //同理,第8位置高电平

有时候我们期望端口某一位设置成高电平,但是其它位的高低电平要保持不变,如何做呢?C语言是很强大的,有办法!如下:

PORTA |=1<<3;  //实现端口A第4位置为高电平,其它位的高低电平不受影响
上面的语句是简化的写法,分解一下就是:
PORTA = PORTA | (1<<3);    //数字1左移3位后与端口A进行按位或,结果就是端口A第4位置为高电平,其它位的高低电平不受影响

那么大家就会问了,如何实现设置某一位为低电平,其它位的高低电平不变呢?建议大家思考1分钟再看下面的内容。

?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
PORTA &=~(1<<3);  //解释一下,首先将1左移3位变成00001000b,然后再按位取反变成11110111b,然后再与端口A做按位与运算,这样就实现了设置端口A第4位为低电平,其它位的高低电平不变。
分解后的语句为:
PORTA = PORTA & (~(1<<3));  //结果是一样的

将某端口相应位的高低电平翻转,即原来高电平变为低电平,低电平变为高电平,呵呵!好简单呦!

PORTA = ~PORTA;  //将PORTA按位取反后再赋值给PORTA

按位逻辑运算还有一个异或,这个也非常有意思,它能实现电平翻转,有兴趣大家看看书,算是给大家留个想头吧!


到这里基本的IO口置位的操作就聊完了,希望大家积极跟贴进行讨论,讲一讲自己的体会和心得,一起学习学习! 

 

 

如何用C语言设置,清除和检查是否置位I/O 寄存器? 

 

Question

如何用C语言设置,清除和检查是否置位I/O 寄存器?

Answer

在AVR Studio的Help菜单中选择the online AVR Tools User Guide然后打开AVR Assembler --> User Guide --> expression_rs,在这里你能获得很多汇编语言关于算术运算方面的应用。

下面是一些简单和常用的算术运算操作,这个例子是改变或检查AVR控制器PORTB端口上PB6和PB4的bit值。

设置PB6和PB4,同时不改变其他管脚状态

PORTB |= (1<<PB6) | (1<<PB4);

清除PB6和PB4,同时不改变其他管脚状态

PORTB &= ~((1<<PB6) | (1<<PB4));

检查PB6是否被置1

if (PORTB &= (1<<PB6))
{
//Do something, for instance set a value in a variable
}

检查PB6和PB4是否被置1

if ((PORTB &= ((1<<PB6) | (1<<PB4))) == ((1<<PB6) | (1<<PB4)))
{
//Do something, for instance set a value in a variable
}

 

 

AVR单片机I/O口位操作,置位、清位、取反。

2008-06-05 11:50

下面是用的宏定义方式:

#define LED_SET     PORTD |= (1 << PD5)            //位置高
#define LED_CLR     PORTD &= ~(1 << PD5)           //位置低
#define LED_COM     PORTD ^= (1 << PD5)            //位取反
#define LED_R       PIND & (1 << PD5)              //位读取

通过上面的定义就可以在程序中直接操作I/O了。

 

 

动手学AVR单片机八、流水灯C语言程序讲解

已有 776 次阅读  2009-05-20 11:26   标签:  AVR  单片机  C语言  流水  程序  

动手学AVR单片机八、流水灯C语言程序讲解

上一讲我们对流水灯实验的电路实现和程序进行了分析,由于我们使用的是C语言编写AVR单片机程序,很多初学者对于怎样使用C语言来编写单片机程序有着很多迷惑,比如为什么PORTB = 0Xff这句话就能实现将单片机的PB口输出高电平。这一讲我们就来进行一下C语言编写AVR单片机程序的入门讲解。

我们还是先把上一讲的程序按行编号后进行分析讲解。

 

#include <avr/io.h>        //io端口寄存器配置文件,必须包含                                          1
#include <util/delay.h>       //GCC中的延时函数头文件                                                    2   

int main(void)            //GCC中main文件必须为返回整形值的函数,没有参数                  3
{
 PORTB = 0Xff;         //PORTB输出高电平,使LED熄灭                                                 4
 DDRB = 0XFF;           //配置端口PB全部为输出口                                                         5
 
 while(1)                                                                                                                        6
 {
  unsigned char Flow_LED,Delay500ms;     //定义流水灯循环次数以及延时时间变量         7      
  for(Flow_LED = 0;Flow_LED <= 7;Flow_LED++)  //流水灯从0-7总共循环8次                 8
  {   
   PORTB = 0xff & (~(1 << Flow_LED));         //每次循环中点亮一个LED                         9
   
   for(Delay500ms = 0;Delay500ms < 20;Delay500ms++)    //延时500ms                             10
   {
    _delay_ms(100);         //delay.h中的延时1ms函数                                                          11
   }
  }
  
 }
}
AVR单片机C语言编程例子讲解

1、首先一个单片机的C语言程序框架如下:

  //头文件包含(即预定义语句) #include<文件名> 或者#include"文件名"   ,<>和""的区别是程序在编译的时候搜  索头文件的路径不同,<>包含起来的文件先搜索编译软件安装目录下是否有这个文件,而""包含起来的文件则是先从我们编写程序存放的目录开始搜索,搜不到则再去搜索安装目录下的文件。习惯上我们在包含编译软件自带的头文件时使用<>,而包含我们自定义的头文件时使用"";

     //全局变量定义,定义单片机程序中需要使用的全局变量,如果没有,这一块可以省略;

//函数声明             声明主函数中需要调用的子函数

主函数                 格式为int main(void)  ,用 大括号{}把主函数的内容包含起来

{

局部变量定义        定义本函数中使用到的变量,没有可以省略

初始化代码           主要是对单片机上电后的端口,寄存器,以及变量进行初始化

while(1)                 每个单片机C语言主程序必须包含一个死循环,用{}将循环体中需要执行的代码包括起来,

                            确保单片机程序能够一直执行

{

  程序代码           需要循环执行的程序代码

}

 

函数定义           对函数声明中声明的函数进行定义,即定义函数需要实现的功能,编写相应程序代码

 

2、本程序的第一、第二两行是预定义语句,#include <文件名>的意思是包含(include的英文本意就是"包含")一个文件,通知编译器在编译程序的时候要将“我 ”这里包含的文件一起编译。io.h是GCC中对AVR单片机的各种寄存器进行标识的一个文件,主要是用一些含义明显的标识符来定义各种寄存器。delay.h是GCC编译器中定义的一些延时函数,有了这些延时函数我们就不必每次编写程序的时候都要自己辛苦的编写延时函数了,直接调用编译器定义好的延时函数即可。

3、PORTB = 0Xff;        DDRB = 0XFF;   这两句话是对寄存器进行赋值,其中PORTB和DDRB就是io.h头文件中定义好的寄存器标识符,我们直接使用就可以了(从这里可以看出预编译指令里包含的头文件的重要性),这里用到了标准C语言的两个知识:赋值表达式和赋值语句,赋值表达式与赋值语句的区别从程序里面来看就是赋值语句后面比赋值表达式多了一个“;”。用C语言编写AVR单片机程序的最主要内容其实就是使用赋值语句给各种寄存器赋值(给寄存器赋予不同的值将使单片机相应模块处于不同的工作状态),或者是读取各个寄存器的值(获得模块的工作状态等信息)。

4、unsigned char Flow_LED,Delay500ms;是变量的定义语句,由于是在函数内部,所以它们是局部变量,其实这个变量定义的位置有些不大规范,标准一点的做法是将这一语句放在程序的第四行之前。一般情况下局部变量的定义尽量放在一个函数的开始部分。局部变量定义的位置不同,它的作用域也就不同,关于变量的作用域请查看相关的C语言书籍。这里不作深究。变量是单片机程序中经常使用的。AVR单片机C语言中的变量与标准C语言的大致相同,但是需要提醒的是,在单片机中定义的变量应该避免使用实型变量,如果实在需要实型变量,应使用整形变量加小数点的方法。这样可以提高程序效率。

5、第8、9、10、11这几行包含了两个循环语句,单片机C语言中的循环语句的定义和使用与标准C语言中的循环语句的定义和使用相同。

 

以上我们分析完了流水灯C语言程序的总体结构和语句功能,在实际使用中不同的程序其结构一般不会脱离这个框架(函数声明和定义部分有的人喜欢不声明函数,直接在主函数之前定义子函数,但这种方法不直观,使程序的可读性变差,不提倡)。

新学AVR单片机C语言的时候会感到有些不知如何下手,其实这是很多人都会有的感觉,不必感到恐慌,我们可以先从最简单的程序入手,对照C语言教材,一行一行跟C语言对照,分清哪些是变量定义、预定义指令、赋值语句、选择语句、循环语句等,仔细分析几个程序后就会有豁然开朗的感觉了。

 

 

以下转贴一位高人对使用单片机C语言编程时需要从标准C语言中学习的知识,其实用C语言编写单片机程序时,用到的标准C语言知识是很少的一部分:

单片机 C 语言入门的一点体会(转帖) 

单片机 C 语言入门的一点体会
一般的 C 语言教材中都具备以下几个章节,在刚刚开始学"单片机"的 C 语言时,我认为有些章节暂时不用学习:

1:C 语言的概述(学)

2:C 语言的绘图基础(暂时不用学习)

3:C 语言的数据类型(学)

4:C 语言的运算符、表达式、语句(学)

5:C语言的基本输入输出函数(就是printf之类函数,暂时不学也罢,因为这些函数的功能非常强大,这样也注定了它比较复杂,结果就是这些函数在单片机中占用资源非常恐怖,还不如自己编写)

6:顺序结构程序设计(学)

7:选择结构程序设计(学)

8:循环结构程序设计(学)

9:数组(学习一下一维数组就可以了,二维多维暂时不用学习)

10:指针(一定要掌握,但是入门时也不必学习,就好像BASIC没有指针,但一样可以写程序)

11:函数(就是主函数,自定义函数之类,学)

12:预处理(学)

13:结构体与共用体(了解一下,暂时不用学习)

14:位运算(学)

15:文件操作(暂时不用学习)

 

手学AVR单片机十、8位数码管显示的程序实现
我们接着来完成8位数码管的显示实验。现在我们开始动手编写程序;
根据前面的介绍,我们应该已经能够知道编写一个AVR单片机C语言程序的基本步骤和方法了。
下面给出这个程序的主程序文件,在这个程序中我们应该能够知道这个程序都包含了那几块,具体来说我们应该能够在这个程序中把以下几个部分找出来:预编译语句、全局变量的定义、函数的声明、主函数、函数定义。如果你还不能够准确找出这几部分,那么需要把前面的内容再详细阅读一下。
主程序代码
#include <avr/io.h>        //io端口寄存器配置文件,必须包含
#include <util/delay.h>       //GCC中的延时函数头文件
#include "hc595.h"

//unsigned char Led_Disbuf[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};  //共阴极
unsigned char Led_Disbuf[10]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};    //共阳极
unsigned char ComBuf[8] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

//函数声明
extern void Delayus(unsigned int lus);         //us延时函数
extern void Delayms(unsigned int lms);        //ms延时函数
int main(void)            //GCCmain文件必须为返回整形值的函数,没有参数
{
unsigned char i;

PORTB = 0xff;         //PORTB输出低电平,使LED熄灭 
DDRB = 0xFF;           //配置端口PB全部为输出口

HC595_port_init(); 

while(1)
{
  for(i = 0; i < 8;i++)
  {
   PORTB = Led_Disbuf;          //送段码
   HC595_Send_Data(ComBuf);     //选通位选端口
   Delayus(70);                    //延时
   HC595_Send_Data(0x00);          //位选通关闭
   
  }
}
}
//us级别的延时函数
void Delayus(unsigned int lus)
{
while(lus--)
{
  _delay_loop_2(4);      //_delay_loop_2(1)是延时4时钟周期,参数为4则延时16
             //个时钟周期,本实验用16M晶体,则16个时钟周期为16/16=1us
    }
}
//ms级别的延时函数
void Delayms(unsigned int lms)
{
while(lms--)
{
  Delayus(1000);        //延时1ms
    }
}

        在这个主程序文件中有人可能会注意到有一些我们前面没有介绍过的内容,比如extern这个单词,它在这里起什么作用?
       这就牵涉到C语言的关键字了,关于C语言的关键字,我们可以到相关的C语言教材中去做详细了解,在这里我们只针对extern这个关键字作出解释,我们注意到extern用在一个函数声明的地方,它的作用就是把这个函数声明为外部函数,这样我们在整个项目的所有文件中就都可以调用这个函数了。同理extern也可以用来声明一个变量为外部变量。

      我们可能还会有一个疑惑:#include "hc595.h"这个头文件包含语句中的hc595.h有什么作用,它是GCC提供的还是我们自己编写的?
      这里我们就要学习C语言中的一个重要的概念:模块化程序设计。何为模块化程序设计?它实现什么功能?如果要铺开来讲的话,可能需要一本书的内容。我们耗不起这个时间和精力。其实我们只需要知道,模块化程序设计是为了简化程序容量而采取的一种将一个程序分成不同的模块,然后通过特定的方法将这些模块组合起来共同完成同一个目标。通俗的说就是化整为零。
    在我们刚开始学习单片机的时候,我们编写的程序都很简单,程序量也不大,所以往往涉及不到模块化程序设计,但是这是一种很好的编程思路,我们有必要掌握。本实例就是采用的这种方法。
     模块化程序设计的思路是:将实现相同功能的程序单独编写,然后实现一个综合的功能,举个例子,我们想实现一个液晶显示的温度测量程序,那么我们可以把液晶显示相关的程序放在一个文件中,把温度测量的程序放在另一个文件中,最后在主程序中调用这两个文件来实现整体的功能。

    通常我们在进行模块化程序设计的时候,常常将变量定义,端口设置,函数声明等部分保存在一个.h文件中,而将函数定义部分放在一个.c文件中,在编写主程序文件的时候,用预处理命令#include.h文件包含起来,而在编译的时候将所有用到的.c文件一起编译。这样就实现了模块化文件的整合。
    
在本实例中,我们将74HC595相关的变量定义,端口定义,函数声明放在74HC595.h文件中,而主程序中的#include "hc595.h"这句话实现了将这个文件包含到主程序中的功能。
下面是本实例中模块化程序设计的.h文件

/*****************************
74hc595.h
***********************************/
/*74hc595单片机的引脚连接
/MR(10)          VCC   低点平时将移位寄存器的数据清零。通常将它接Vcc
/OE(13)          PG4   高电平时禁止输出(高阻态)。
                         如果单片机的引脚不紧张,用一个引脚控制它,
       可以方便地产生闪烁和熄灭效果。比通过数据端移位控制要省时省力。
ST_CP(12)        PG1    上升沿时移位寄存器的数据进入数据存储寄存器,
                          下降沿时存储寄存器数据不变。通常将RCK置为低电平,
        当移位结束后,在RCK端产生一个正脉冲(5V时,大于几十纳秒就行了。
        通常都选微秒级),更新显示数据。
SH_CP(11)        PG0     上升沿时数据寄存器的数据移位。QA-->QB-->QC-->...-->QH
                         下降沿移位寄存器数据不变。(脉冲宽度:5V时,大于几十纳秒就行了。
        通常都选微秒级)
DS(14)           PG2      串行数据输入端。
*/
#ifndef __HC595_H__
#define __HC595_H__
#include <avr/io.h>        //io端口寄存器配置文件,必须包含
#include <util/delay.h>       //GCC中的延时函数头文件

#define HC595_latch   (1 << PG1)    //上升沿数据打入8位锁存器,下降沿锁存器数据不变
#define HC595_sclk    (1 << PG0)     //上升沿数据移位,下降沿数据不变
#define HC595_oe      (1 << PG4)    //低电平,8位数据锁存器输出,高电平输出高组态
#define HC595_data    (1 << PG2)     //串行数据输入端

#define SET_HC595_latch   (PORTG |= (1 << PG1))
#define CLR_HC595_latch   (PORTG &= ~(1 << PG1))
#define SET_HC595_sclk    (PORTG |= (1 << PG0))
#define CLR_HC595_sclk    (PORTG &= ~(1 << PG0))
#define SET_HC595_data    (PORTG |= (1 << PG2))
#define CLR_HC595_data    (PORTG &= ~(1 << PG2))
#define SET_HC595_oe      (PORTG |= (1 << PG4))
#define CLR_HC595_oe      (PORTG &= ~(1 << PG4))

void HC595_port_init(void);          //595端口初始化
void HC595_Send_Data(unsigned char byte);        //发送一个字节
void HC595_Output_Data(unsigned char data);      //发送字符串
#endif

同时我们将与74HC595相关的函数定义部分放在74HC595.c文件中,如下

/********************************
74hc595.c
**************************************/
#include "hc595.h"
//595端口初始化
void HC595_port_init(void)          
{
PORTG = 0x00;
DDRG |= (1 << PG0) | (1 << PG1) | (1 << PG2) | (1 << PG4);
}
//发送一个字节
void HC595_Send_Data(unsigned char byte)
{
unsigned char i; 
//CLR_HC595_latch;
for(i = 0;i < 8;i++)
{
  
  if(byte & 0x80)
  {
   SET_HC595_data; 
  }
  else
  {
   CLR_HC595_data;
  }
  byte <<=1;
  
  SET_HC595_sclk;         //上升沿数据移位
  CLR_HC595_sclk;
   
}
  
SET_HC595_latch; 
CLR_HC595_latch;



}
//发送字符串
void HC595_Output_Data(unsigned char data)
{
CLR_HC595_latch;         //下降沿锁存器数据不变
HC595_Send_Data(data);
SET_HC595_latch;           //上升沿数据打入8位锁存器
}

在主程序中我们使用预定义语句将.h文件包含到了主程序文件中,那么我们怎样实现将.c文件编译到整个项目程序中呢?在这里我们只要在makefile文件中将这个.c文件加进去就可以了,如下图所示,在SRC = $(TARGET).C的后面空一格,然后输入我们所定义的.c文件的名称,然后保存makefile文件的更改。最后进行编译就可以了,编译的时候如果我们仔细观察编译器的输出信息,会发现不但编译了main.c文件,同时也编译了74hc595.c文件。

 

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值