http://www.eefocus.com/xindong1492/blog/11-02/203970_ebd6f.html
对一般PIC的端口操作建议
鄙人比较熟悉PIC18,在PIC18中I/0常用有三个寄存器,稍微了解PIC的朋友,应该也很熟悉TRIS、PORT、LAT。根据一些自己实际经验以及一些指导书的提示,我们在对I/0端口操作时,最好知道一下以下几点:
1. 复位后状态:
复位以后,所有引脚被配置为输入。
2. 初始化操作:
建议在初始化端口时,首先初始化该PORT的数据锁存器(LAT或PORT寄存器),然后再初始化数据方向寄存器(TRIS)。这样将排除引脚电平出现毛刺的可能性,因为LAT 寄存器 (PORT数据锁存值)的上电状态是随机的。
3. 对于“读-修改-写操作”的理解:
任何执行写操作的指令实际都是在内部执行读-修改-写操作。若端口引脚在输入和输出状态之间切换,则执行写端口指令时必须小心。
例如,BSF PORTB, 5指令将导致PORTB 的全部 8位读入 CPU。然后该指令将 bit 5 置位并将最终数据写入 LATB。
如果 RB7
引脚为双向I/O,并且当 BSF PORTB, 5执行时定义为输入引脚,则会将引脚的输入信号本身读入 CPU
并写入LATB<7>,覆盖以前的内容。只要该RB7引脚保持输入模式,不会出现问题。然而,如果 RB7
引脚被切换为输出模式,数据锁存器中的内容就处于未知状态,导致RB7 引脚状态错误。
对 PORTB和TRISB 执行读-修改-写操作会影响 RB7
; RB<7:0> = 0001 0110
; LATB = 1001 0110
; TRISB = 1100 0000
bsf PORTB,5 ; read-modify-write operation.
; LATB = 0011 0110 bit 7 cleared
; RB<7:0> = 1011 0110 RB7 changes to high speed
bcf TRISB,7 ; changes RB7 from input to output
; TRISB = 0100 0000
; RB<7:0> = 0011 0110 RB7 in now driven low
一种较好的解决方案:是使用数据锁存器。指令 BSF LATB, 5将读输出锁存器中的各位,将 bit 置 1,并将结果写回到输出锁存器。这样就不会出现 LATB<7>被改变的风险了。
对 LATB和TRISB 执行读-修改-写操作不影响 RB7
; RB<7:0> = 1001 0110
; LATB = 1001 0110 bit 7 is high
; TRISB = 1100 0000
bsf LATB,5 ; read-modify-write operation
; LATB = 1011 0110 bit 7 has not changed
; RB<7:0> = 1011 0110
bcf TRISB,7 ; changes RB7 from input to output
; TRISB = 0100 0000
; RB<7:0> = 1011 0110 RB7 remains high
4. PORT和 LAT寄存器之间的差异可以归纳如下:
有次偶尔在论坛上看见有人提问PORT和LAT有什么区别?现在把它归纳在此!
写 PORTx寄存器就是将数据值写入该端口锁存器。
写 LATx寄存器就是将数据值写入该端口锁存器。
读 PORTx寄存器就是读取I/O 引脚上的数据值。
读 LATx寄存器就是读取保存在该端口锁存器中的数据值。
5. 小结:
a) 输出时,赋值常用LATx寄存器;
b) 输入时,取值常用PORTx寄存器;
对PIC32MX的I/O端口操作的比较
当然前面所讲对PIC基本上中上系列的都适用;而针对PIC32MX,PIC32MX的I/0操作在以前PIC单片机的基础上又增加了很多功能,更加完整、全部,同时也方便用户使用。
除了TRIS、PORT、LAT三个基本寄存器外,还增加了SET, CLR, INV I/O 三种针对端口快速位操作PIC32MX还有ODC(Open-Drain Control register)寄存器可以实现针对每个独自的管脚是否使能漏极开路输出和CNPUE寄存器实现可单独使能/禁止输入引脚的弱上拉(当然这个可以配合监视选择性输入并在检测到引脚电平状态发生变化时产生中断使用)。的寄存器,除此之外,
对于这些寄存器说明在PIC32MX的数据手册里都可以找到,这里就不作详细说明。
我们在讲讲如何来操作这些I/0端口:
下面这次是自己在学习时一个小程序,和大家分享一下:
/*************************************************
* 项目名: i/o操作比较演示
* 效果: 开始效果----三个小灯逐一点亮;后三个小灯逐一熄灭
* 按下SW1后,变成流水灯,再按一下SW2,也变成开始的效果
* 平台: PIC32MX ETHERNET STARTER KIT
* 作者: Andy
* 日期: 2011/1/26
*************************************************/
#include
#define SYS_FREQ (80000000ul)
// 配置位设置......
// SYSCLK = 80 MHz (8MHz Crystal/ FPLLIDIV * FPLLMUL / FPLLODIV) 系统时钟
// PBCLK = 40 MHz 外部总线时钟
// Primary Osc w/PLL (XT+,HS+,EC+PLL)总晶振
// WDT OFF 关看门狗
// Other options are don't care
#pragma config FPLLMUL = MUL_20, FPLLIDIV = DIV_2, FPLLODIV = DIV_1, FWDTEN = OFF
#pragma config POSCMOD = HS, FNOSC = PRIPLL, FPBDIV = DIV_8
//======这里有个ReadCoreTimer()或许大家都不怎么了解======
/**************************************************
* 从PIC32 Family Reference Manual, Sect. 02 CPU中可以查个PIC32MX
* 在内核中多了个Core Timer;说简单点也就是个计数器;而且这个
* 计数器是每两个系统时钟周期加1;所以也就是说:ReadCoreTimer()这
* 语句执行一次消耗两个系统时钟周期的时间。
* 我们在计算一下:(以延时1ms为例)
* SYS_FREQ = 80000000;
* tWait = 80000000/2000 * 1 = 400000;
* 也就是 ReadCoreTimer()要执行400000次;
* 消耗时间 400000*1 / 80000000 * 2(两个系统周期加1) = 1ms
**************************************************/
void DelayMs(unsigned int msec)
{
unsigned int tWait = 0, tStart = 0;
tWait = (SYS_FREQ/2000)*msec;
tStart = ReadCoreTimer();
while ((ReadCoreTimer() - tStart) < tWait);
}
int main(void)
{
//=============位操作===========
// _RD0 = 0;
// _RD1 = 0;
// _RD2 = 0;
//---- 这三种操作都是相同的结果----
// _TRISD0 = 0;
// _TRISD1 = 0;
// _TRISD2 = 0;
//===========CLR寄存器操作========
// LATDCLR = 0x0001 | 0x0002 | 0x0004;
// TRISDCLR = 0x0001 | 0x0002 | 0x0004;
//=========Peripheral-Library操作======
mPORTDClearBits(BIT_0 | BIT_1 | BIT_2);
mPORTDSetPinsDigitalOut(BIT_0 | BIT_1 | BIT_2);
//=====因为PIC32MX ETHRNET Kit中SW1..SW3没有接上拉电阻====
//==========所以最好应该内部使能上拉===
ConfigCNPullups(CN15_PULLUP_ENABLE | CN16_PULLUP_ENABLE);
//===============设置按键输入=====
mPORTDSetPinsDigitalIn(BIT_6 | BIT_7 | BIT_13);
while (1)
{
DelayMs(200);
// LATDINV = 0x0001;
mPORTDToggleBits(BIT_0);
DelayMs(200);
// LATDINV = 0x0002;
mPORTDToggleBits(BIT_1);
DelayMs(200);
// LATDINV = 0x0004;
mPORTDToggleBits(BIT_2);
// if (_RD6 == 0) //===判断SW1是否按下===
//=======这两条语句也相同的作用=========
if (mPORTDReadBits(BIT_6) == 0)
{
DelayMs(10); //===消抖===
// if (_RD6 == 0) //===确认====
if (mPORTDReadBits(BIT_6) == 0)
{
while (1)
{
DelayMs(200);
// LATD = 0x0004;
//========这几两条语句也相同的效果===========
mPORTDWrite(0x0004);
DelayMs(200);
// LATD = 0x0002;
mPORTDWrite(0x0002);
DelayMs(200);
// LATD = 0x0001;
mPORTDWrite(0x0001);
// if (_RD7 == 0)
if (mPORTDReadBits(BIT_7) == 0)
{
DelayMs(10);
// if (_RD7 == 0)
if (mPORTDReadBits(BIT_7) == 0)
{
// LATD = 0x0000;
mPORTDClearBits(BIT_0 | BIT_1 | BIT_2);
break;
}
}
}
}
}
}
return 0;
}
/********************************************
* 其实从本例中可以看出:
* 简单的端口操作,其实使不使用Peripheral-Library函数
* 没有太大的差别,都是一句话的操作;
* Peripheral-Library函数更抽象点,你应该更加可以理解些
* 而自己直接对寄存器的操作,虽然看起来不怎么人性化;
* 但自己对内部寄存器有了更加深刻的理解。
* 两个方法都有自己的利弊,当然也要看个人爱好。
* 当然值得肯定是:Peripheral-Library在后续一些复杂应用
* 中有着很好的作用。-------by Andy
********************************************/
学习补充——对Core Timer的理解:
对Core Timer的理解:
PIC32 Family Reference Manual, Sect. 02 CPU上的定义:
The PIC32MX architecture includes a core
timer that is available to application programs. This timer is
implemented in the form of two co-processor registers–the Count register
(CP0_COUNT), and the Compare register (CP0_COMPARE). The Count register
is incremented every two system clock (SYSCLK) cycles. The incrementing
of Count can be optionally suspended during Debug mode. The Compare
register is used to cause a timer interrupt if desired.An interrupt is
generated when the Compare register matches the Count register. An
interrupt is taken only if it is enabled in the interrupt controller.
自己比较“锉”的翻译,呵呵,大家有能力还是自己看原文好…..呵呵….
内核定时器:
PIC32MX
构架中包涵一个直接可以被应用程序所用的“内核定时器”。这个定时器是由两个协同合作的寄存器组成而正常工作的——计数器(CP0_COUNT)和比较器(CPO_COMPARE).这个计数器每两个系统时钟周期(SYSCLK)加一,而且计数器可以选择性地被暂定在调试(Debug)模式下。比较器是用来引起计数器中断,当然前提你希望这样去做。当比较器中的值与计数器的值相同时,就会产生中断,当然前提是中断的正常工作,需要在中断控制器中使能。
网友评论