打印

LPC1343学习笔记(连载中)

[复制链接]
楼主: LPC300
手机看帖
扫描二维码
随时随地手机跟帖
41
LPC300|  楼主 | 2010-6-28 22:53 | 只看该作者 回帖奖励 |倒序浏览
从以上注释可以看出,设置定时器为PWM模式的步骤:

1、
首先要使能定时器,才能对定时器进行设置;

2、
打开定时器时钟;

3、
使能各个匹配寄存器;

4、
切换IO功能;

5、
制定存放PWM周期值的寄存器;

6、
写入周期;

7、
设置各通道占空比;

8、
允许当计数值和MAT3寄存器值匹配时请0计数值;

Ps:可以注意到,在此函数中已经把各个匹配输出通道设置为50%的占空比。

回到主函数,接下来使能定时器:

enable_timer32(1);

接着设置占空比:

setMatch_timer32PWM (1, 0, period/4);

原型如下:

void setMatch_timer32PWM (uint8_t timer_num, uint8_t match_nr, uint32_t value)

{


if (timer_num)


{


switch (match_nr)


{


case 0:LPC_TMR32B1->MR0 = value;break;


case 1:LPC_TMR32B1->MR1 = value;break;


case 2:LPC_TMR32B1->MR2 = value;break;


case 3:LPC_TMR32B1->MR3 = value;break;


default:break;


}

}


else


{


/*
此处是对另一个定时器进行设置,与上类同*/


}

}

同样轻易看出,该函数是对各个MAT寄存器(在初始化函数中已经写入了50%的占空比数值)进行设置,以达到改变占空比的目的。

最后提一提PWM的周期如何确定呢,我们已经知道PWM周期以MATCH3寄存器中的数值N决定,所以定时器计数N次,就是该PWM波形的单周期。在本次例程中,NXP将周期值设为N=1000,所以PWM的周期是(1000/72000000s,频率便是72KHz

这样,在一定的时间间隔离改变占空比,就可以实现LED灯渐亮的效果了。




下一节,我们来看看定时器的第三个功能:捕获

使用特权

评论回复
42
米其林r| | 2010-6-28 22:56 | 只看该作者
楼主太牛,必须每天都顶!

使用特权

评论回复
43
手写识别| | 2010-6-28 22:56 | 只看该作者
其实关于时钟,版主有没有可以外部设备进行效验的方式,这样可以检测我们计算出来的每个时钟,定时的准确性?亦或是将内部时钟引出来检测的方式,或是通过IO口,通过AHB设置IO口时钟,然后翻转IO来检测IO管脚时钟的方式检测内部时钟系统的正确性,谢谢站送回复!或是有回复的话,站送消息,我来拜读也可以,谢谢!

使用特权

评论回复
44
无语凝咽| | 2010-6-28 22:57 | 只看该作者
顶,对照手册跟你的说明,很快就弄明白了。

使用特权

评论回复
45
LPC300|  楼主 | 2010-6-28 22:57 | 只看该作者
定时器有三个内容,定时、PWM还有捕获。本节是最后一个:捕获。
捕获功能是指,定时器可以在某个引脚发生某种电平变化的时候,做出相应的反应。在《LPC130 user mannal》里面对定时器捕获功能进行描述的寄存器只有2个,是很简单的。我们从这两个寄存器剖析32位定时器的捕获功能。



第一个:捕获控制寄存器




18.jpg (58.04 KB)
2010-6-7 12:41



从这个寄存器我们可以看到,定时器在捕获某个引脚有电平变化时,产生对应反应:

1、当对应引脚产生上升沿时,将TC的内容装入CR0;

2、当对应引脚产生下降沿时,将TC的内容装入CR0;

3、CR0装载事件的触发,导致一个中断的产生;



CR0是什么呢?是捕获寄存器:


各捕获寄存器(TMR32B0CR0-地址0x4001 402C和TMR32B1CR0-地址0x4001 802C)与器件管脚相关联,当管脚发生特定的事件时,可将定时器计数器的值装入该捕获寄存器。捕获控制寄存器中的设置决定是否使能捕获功能,以及在相关管脚的上升沿、下降沿或双边沿时是否产生捕获事件。

以上两个寄存器就是用来设置定时器捕获功能的所有寄存器,是很简单的。



直白的说,捕获功能就是在某个引脚产生跳变事件之时,将当前定时器值记录,并根据用户的设置选择是否产生中断。



由此就很轻易能想到,利用这个捕获功能我们可以计算出两次捕获事件的时间差,还有记录跳变沿的数目。这次我们尝试设计一个实验,记录LED闪烁的次数。程序由PWM示例更改而来,



首先是主函数:

int main (void)

{

init_timer32(0, TIME_INTERVAL);//初始化32定时器0,设置定时间隔

enable_timer32(0);//使能32位定时器0

LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);//开启AHB时钟

GPIOSetDir( LED_PORT, LED_BIT, 1 );//设置唯一个LED的IO

/*此大循环里面为模拟50%的PWM信号驱动LED闪烁*/

while (1)

{

if ( (timer32_0_counter > 0) && (timer32_0_counter <= 50) )

{

GPIOSetValue( LED_PORT, LED_BIT, LED_OFF );

}

if ( (timer32_0_counter > 50) && (timer32_0_counter <= 100) )

{

GPIOSetValue( LED_PORT, LED_BIT, LED_ON );

}

else if ( timer32_0_counter > 100 )

{

timer32_0_counter = 0;

}

}

}



我们在前一节已经讲述了关于定时器时间间隔的设置过程,在此不在阐述,由此大循环里面的模拟PWM也非常容易看懂的了。timer32_0_counter在0-100之间计数,逢50跳转高电平,逢100跳转低电平,由此得到周期为100,高电平周期为50的50%占空比的PWM信号。



大家肯定很容易猜得到,在这个主函数里面对定时器捕获功能进行设置的肯定在

init_timer32(0, TIME_INTERVAL);

里面,所以我们再次剖开它:

void init_timer32(uint8_t timer_num, uint32_t TimerInterval)

{

if ( timer_num == 0 )

{

/* Some of the I/O pins need to be carefully planned if

you use below module because JTAG and TIMER CAP/MAT pins are muxed. */ LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9);

LPC_IOCON->PIO1_5 &= ~0x07;
/*
Timer0_32 I/O config */


LPC_IOCON->PIO1_5 |= 0x02;
/* Timer0_32 CAP0 */


LPC_IOCON->PIO1_6 &= ~0x07;

LPC_IOCON->PIO1_6 |= 0x02;
/* Timer0_32 MAT0 */


LPC_IOCON->PIO1_7 &= ~0x07;

LPC_IOCON->PIO1_7 |= 0x02;
/* Timer0_32 MAT1 */


LPC_IOCON->PIO0_1 &= ~0x07;


LPC_IOCON->PIO0_1 |= 0x02;
/* Timer0_32 MAT2 */


#ifdef __JTAG_DISABLED

LPC_IOCON->JTAG_TDI_PIO0_11 &= ~0x07;


LPC_IOCON->JTAG_TDI_PIO0_11 |= 0x03;
/* Timer0_32 MAT3 */


#endif

#ifdef TIMER32_0_DEFAULT_HANDLER

timer32_0_counter = 0;

timer32_0_capture = 0;

#endif //TIMER32_0_DEFAULT_HANDLER

LPC_TMR32B0->MR0 = TimerInterval;

#if TIMER_MATCH

LPC_TMR32B0->EMR &= ~(0xFF<<4);

LPC_TMR32B0->EMR |= ((0x3<<4)|(0x3<<6)|(0x3<<8)|(0x3<<10));
/* MR0/1/2/3 Toggle */


#else

/* Capture 0 on rising edge, interrupt enable. */

LPC_TMR32B0->CCR = (0x1<<0)|(0x1<<2)|(0x1<<1);

#endif

LPC_TMR32B0->MCR = 3;
/* Interrupt and Reset on MR0 */



/* Enable the TIMER0 Interrupt */


NVIC_EnableIRQ(TIMER_32_0_IRQn);

}

else if ( timer_num == 1 )

{


/*
此部分内容和上半部分类同*/


}

return;

}

这里我们特地做了明显的标记,蓝色部分是一个#if…#else…#endif预编译语句,而蓝色部分包含的红色语句就是这个初始化函数里面对定时器捕获功能的唯一一句设置语句。什么时候会将这个设置语句编译呢?各位肯定知道取决于TIMER_MATCH这个宏定义过的变量。很容易查看到(在timer32.h):

#define TIMER_MATCH
0


可以看到默认情况下,该定时器初始化函数选择捕获功能,另一种情况是外部匹配功能。

LPC_TMR32B0->CCR = (0x1<<0)|(0x1<<2)|(0x1<<1);

使用特权

评论回复
46
LPC300|  楼主 | 2010-6-28 22:58 | 只看该作者
这句什么意思呢?对照寄存器:





19.jpg (59.05 KB)
2010-6-7 12:41



意思就是将上升沿和下降沿捕获,并都产生一次中断。

中断服务函数里面:

void TIMER32_0_IRQHandler(void)

{

if ( LPC_TMR32B0->IR & 0x01 )

{

LPC_TMR32B0->IR = 1;
/* clear interrupt flag */

timer32_0_counter++;

}

if ( LPC_TMR32B0->IR & (0x1<<4) )

{

LPC_TMR32B0->IR = 0x1<<4;
/* clear interrupt flag */

timer32_0_capture++;

}

return;

}

找到这个寄存器说明:





20.jpg (40.45 KB)
2010-6-7 12:41



所以知道IR寄存器的第四位为捕获事件中断标志,所以对于中断服务函数里面的红字部分。

该if函数里面,使用一个timer32_0_capture++变量来记录捕获次数。

所以我们要看LED闪烁了几次,就看这个timer32_0_capture++就可以了。

7次闪烁,运行结果截个图:



21.jpg (68.8 KB)
2010-6-7 12:41



为什么是14呢?因为一次闪烁包含了一个上升沿和一个下降沿呀



下节预告,USART……

使用特权

评论回复
47
linux1| | 2010-6-28 22:58 | 只看该作者
楼主太强悍了,我对你的帖子进行了整理已经100多页了,可以成为学习LPC1343的一部很好的教程了,声明:版权是楼主的!!

使用特权

评论回复
48
yoyowodeai| | 2010-6-28 22:59 | 只看该作者
大家好!这个帖子终于被我发现了!楼主辛苦了。学习中......

使用特权

评论回复
49
3B1105| | 2010-6-28 23:00 | 只看该作者
小弟刚参加工作,工作经验实在缺乏。公司以前用的是51单片机,现在研发新的产品,采用的是 NXP LPC1114 这款片子。要驱动触摸屏(ads7846),并和DSP之间通讯。不知道该如何做,大侠们,有谁写过相关的代码,给小弟参考一下,只求驱动触摸屏这部分代码。        应应急,跪求......

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
xiaoyuze + 1 很给力!
50
LPC300|  楼主 | 2010-6-28 23:01 | 只看该作者
本节我们来使用LPC1343UART接口做一个简单的收发实验。
大家之前应该都有使用51AVR一类单片机做过异步串行收发实验,当然串口在电子开发中的应用地位就无需多言。我们直接进入主题。




本次试验这样设计,用PC作为上位机向UART发送一个(串)字节,然后LPC1343收到这个(串)字节后再发回UART,PC上的串口观察软件显示出来。




我们来看NXP带给我们的UART例程来看看UART的设置以及工作过程。首先是主函数:

int main (void)

{


UARTInit(115200);//
初始化UART接口并设置为波特率115200NVIC也在内一并设置

while (1)


{



if ( UARTCount != 0 )



{




LPC_UART->IER = IER_THRE | IER_RLS;/*
禁用接受缓存寄存器*/



UARTSend( (uint8_t *)UARTBuffer, UARTCount );//
发送数据



UARTCount = 0;


/* 重新使能接收缓冲寄存器*/

LPC_UART->IER = IER_THRE | IER_RLS | IER_RBR;



}



}


}



从主函数就可以看到本次例程的目的了:初始化UART——一旦接收到数据之后立即停止接收——发送——开启下一次接收。最重要的当然是初始化函数UARTInit()

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

** Function name:
UARTInit


**

** Descriptions:
Initialize UART0 port, setup pin select,


**

clock, parity, stop bits, FIFO, etc.


**

** parameters:
UART baudrate


** Returned value:
None


**

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

void UARTInit(uint32_t baudrate)

{


uint32_t Fdiv;



uint32_t regVal;





UARTTxEmpty = 1;



UARTCount = 0;






NVIC_DisableIRQ(UART_IRQn);
//
关闭UART中断,避免此后的初始化有中断打断

LPC_IOCON->PIO1_6 &= ~0x07;
/*
UART
IO口设置 */


LPC_IOCON->PIO1_6 |= 0x01;
/* UART
接收IO */


LPC_IOCON->PIO1_7 &= ~0x07;



LPC_IOCON->PIO1_7 |= 0x01;
/* UART
发送IO */


/* Enable UART clock */



LPC_SYSCON->SYSAHBCLKCTRL |= (1<<12);



LPC_SYSCON->UARTCLKDIV = 0x1;
/*UART
时钟分频器进行1分频*/




LPC_UART->LCR = 0x83;
/* 8
位,无校验,1停止位,并开启除数锁存器*/


regVal = LPC_SYSCON->UARTCLKDIV;


Fdiv=(((SystemCoreClock*LPC_SYSCON->SYSAHBCLKDIV)/regVal)/16)

//baudrate ;
/*
计算波特/**/




LPC_UART->DLM = Fdiv / 256;
//
写入波特率计算值高位



LPC_UART->DLL = Fdiv % 256;
//
写入波特率计算值低位


LPC_UART->LCR = 0x03;
/*
关闭除数锁存器*/


LPC_UART->FCR = 0x07;
/* Enable and reset TX and RX FIFO.
开启与复位RXTX*/

/*先入先出功能 */


regVal = LPC_UART->LSR;/*
读取线状态寄存器以清除该寄存器内容*/

/*确保TXRXFIFO队列都是清空的*/


while (( LPC_UART->LSR & (LSR_THRE|LSR_TEMT)) != (LSR_THRE|LSR_TEMT) );



while ( LPC_UART->LSR & LSR_RDR )



{



regVal = LPC_UART->RBR;
/* FIFO
中有未读数据则读出*/


}






/*
使能UART中断 */


NVIC_EnableIRQ(UART_IRQn);




#if TX_INTERRUPT
//
是否使用发送中断,本次例程使用


LPC_UART->IER = IER_RBR | IER_THRE | IER_RLS;
/* Enable UART interrupt */


#else
//
所以执行此else


LPC_UART->IER = IER_RBR | IER_RLS;
/*
启用缓存数据可用中断、线状态中断,同时也*/

/*开启了字符超时中断*/

#endif


return;


}

使用特权

评论回复
51
LPC300|  楼主 | 2010-6-28 23:01 | 只看该作者
打星号的地方是笔者认为比较值得关注的地方:

1UARTIO口设置,根据上述函数中的语句查找相关寄存器,可以发现它将P16P17设置为:UART_RXDUART_TXD功能;

2、选择UART时钟分频数,此处1分频,和波特率设置有直接关系;

3、选择数据格式,此处选择数据长度8位,无校验,1位停止位,并开启除数锁存;

4、除数锁存器:分为LSB8位)和MSB8位),用来填入对应某波特率的计数值,更改之前解除锁定,更改完毕恢复锁定,这样就可以锁定波特率了(可以这样简单的理解);

5、计算波特率,此处是重点了。首先我们肯定知道系统核心频率为72MHz,即SystemCoreClock=72 000 000(参考本系列前几章内容)。而UART作为AHB总线上的设备,自然要经过AHB分频器,在此处,AHB分频系数并未做过特别设置,所以为默认值1。时钟经过AHB分频之后要经过UART分频器进行再分频,分频系数仍为1(第2点)。因此我们来计算这个公式:

Fdiv = (((SystemCoreClock*LPC_SYSCON->SYSAHBCLKDIV)/regVal)/16)/baudrate

其中SystemCoreClock=72000000LPC_SYSCON->SYSAHBCLKDI=1regVal=1baudrate=115200,所以可以计算出:

Fdiv=39.0625≈0x27

这个便是产生115200波特率所要填入除数锁存器的值。逆过来就可以计算出计数值对应的波特率。

6、线状态寄存器(下文稍加讲述)是以读操作来清空的;

其实这个函数,对于用户来说,只需要填入想要产生的波特率作为函数参数就可以完成LPC1343UART初始化以及波特率设定工作。

设定完成之后,UART就开始工作了,因为初始化函数里面开启了启用缓存数据可用中断、线状态中断所以当有数据从上位机向UART发送数据时,进入中断服务函数:

void UART_IRQHandler(void)

{


uint8_t IIRValue, LSRValue;



uint8_t Dummy = Dummy;


IIRValue = LPC_UART->IIR;



IIRValue >>= 1;




IIRValue &= 0x07;
/*
确定中断源*/


if (IIRValue == IIR_RLS)
/*
是否为线中断 */


{



LSRValue = LPC_UART->LSR;



/*
判断是哪一个中断源 */


if (LSRValue & (LSR_OE | LSR_PE | LSR_FE | LSR_RXFE | LSR_BI))



{



/*
有错误或打断中断 */


UARTStatus = LSRValue; /*
清楚线中断寄存器*/


Dummy = LPC_UART->RBR;
/*
读出数据,并舍弃(因为是错误的),并退出中断服*/

/*务函数*/


return;




}



if (LSRValue & LSR_RDR)
/*
准备接受数据 */



{



/*
如果没有错误信息引发线中断,则保存数据缓存中的数据*/


UARTBuffer[UARTCount++] = LPC_UART->RBR;



if (UARTCount == BUFSIZE)



{



UARTCount = 0;
/*
当缓存溢出时清零计数标志 */


}



}



}



else if (IIRValue == IIR_RDA)
/*
接受到可用数据*/


{



/* Receive Data Available */



UARTBuffer[UARTCount++] = LPC_UART->RBR;



if (UARTCount == BUFSIZE)



{



UARTCount = 0;
/* buffer overflow */



}



}



else if (IIRValue == IIR_CTI)
/*
字符超时指示*/


{



/* Character Time-out indicator */



UARTStatus |= 0x100;
/* Bit 9 as the CTI error */



}



else if (IIRValue == IIR_THRE)
/*
发送完成中断 */


{



/* THRE interrupt */



LSRValue = LPC_UART->LSR;
/* Check status in the LSR to see if



valid data in U0THR or not */



if (LSRValue & LSR_THRE)



{



UARTTxEmpty = 1;



}



else



{



UARTTxEmpty = 0;



}



}



return;


}
这个中断服务函数是一个if…else if….else if结构。在进入此中断服务函数后,读取

使用特权

评论回复
52
LPC300|  楼主 | 2010-6-28 23:01 | 只看该作者
中断标识寄存器判断中断源,选择进入相应的if环节执行相应语句。我们看看UART都有哪些中断。第一个是RLSReceive Line Status即接收线中断:

可以在用户手册查看到接受线中断分别有以下多种:

RDR Receiver Data Ready,接受数据就绪中断;

OEOverrun Error,即溢出错误中断;

PE:Parity Error,校验错误中断;

FE:Framing Error,帧错误中断;

BI:Break Interrupt,间隔状态中断;

THRETransmitter Holding Register Empty,发送保持寄存器空中断;

TEMPTransmitter Empty,发送保持寄存器与临时寄存器空中断;

RXFEError in RX FIFORX错误中断;

对照上述中断服务函数第一个if部分,当判断确定为线中断之后,即判断是否是OE——RXFE中的任何一个错误,如果有错误,则读出数据有效保存,如果有错误,则读出数据但丢弃。

所以,线中断在进行数据校验的场合才会使用。而本次实验中并未用到数据校验位。所以此中断不会进入。

当不使用校验功能之时,收到数据之后会进入第一个else if结构:

else if (IIRValue == IIR_RDA)

{}

在进入此部分之后将数据读出。

当接受一个字符(5~8位不等)超时时,会进入接受字符超时中断部分。

发送完成中断在本次试验中并未使能,略过。

如此我们应该可以预知本次试验中当PC上位机发送一个(串)字符之后,会进入中断服务函数并且进入else if (IIRValue == IIR_RDA{}环节,将收到的数据保存在UARTBuffer中,并使UARTCount++。然后退出中断函数之后,回到主函数,执行发送部分:

if ( UARTCount != 0 )


{




LPC_UART->IER = IER_THRE | IER_RLS;/*
禁用接受缓存寄存器*/



UARTSend( (uint8_t *)UARTBuffer, UARTCount );//
发送数据



UARTCount = 0;


/* 重新使能接收缓冲寄存器*/

LPC_UART->IER = IER_THRE | IER_RLS | IER_RBR;



}


}

找到UARTSend()

void UARTSend(uint8_t *BufferPtr, uint32_t Length)

{


while ( Length != 0 )



{




/* THRE status, contain valid data */


#if !TX_INTERRUPT//未使用中断发送方式,所以编译此部分



while ( !(LPC_UART->LSR & LSR_THRE) );//
等待发送保持寄存器空



LPC_UART->THR = *BufferPtr;//
将待发数据写入发送保持寄存器

#else



/* Below flag is set inside the interrupt handler when THRE occurs. */



while ( !(UARTTxEmpty & 0x01) );




LPC_UART->THR = *BufferPtr;



UARTTxEmpty = 0;
/* not empty in the THR until it shifts out */


#endif


BufferPtr++;



Length--;



}



return;


}

UART数据发送函数,第一个参数要求填入存放待发送数据(注意为8位,即字符型数据)的数组名,第二个参数为待发数据长度。通过注释可以看到发送的过程很简单,等待发送保持寄存器为空后将数据写入发送保持寄存器就完成了发送。

这样我们就将本次的UART收发试验的过程初始化——等待接收——UART中断——保存数据——发送数据分析完毕。

NXP赠送的这个LPC1343并没有挂载UART接口,而只留了P16P17两个IO给用户拓展,所以笔者使用了其他开发板的串口连接,需要注意的是,我们手上的这块开发板是3.3V供电,而市面上比较多采用的MAX2325V供电。所以或者使用LPC1343评估板的5VMAX232供电,或者使用3.3V供电的电平转换芯片。笔者这里使用了3.3V供电的ST232芯片。

导入lpc1343.examples->uart,编译链接。连好硬件,从串口调试终端发送一个(串)数据,可以看到串口调试终端的接收框里准确的显示了我们所发送的数据,本次实验完结。










截图00.jpg (34.85 KB)
2010-6-17 11:27












下节预告,ADC… …










使用特权

评论回复
53
LPC300|  楼主 | 2010-6-28 23:01 | 只看该作者
上一节完成了UART的实验,有了UART之后,我们用以显示实验结果的设备就不是一个简单的LED了。




本节来讲述LPC1343内部ADC的使用。设计一个实验,使用ADC0通道进行AD转换,并且将转换结果通过UART发送在PC端的串口终端软件观察。




这次仍然以NXP提供的example作为例子,但是LPC1343内部ADC工作方式众多,所以该example用了许多的预编译结构,笔者在此将本次实验不会用到的语句全部去掉,程序变得简洁,也更易于理解。

同样的,在此节中将不再将所用到的寄存器一一列出,而只是一个各个寄存器设置的“线索”,因为至此各个读者一定已经拥有了自己翻阅用户手册查看对应寄存器内容的能力。




从主函数我们可以看出本次实验的进展过程:

int main (void)

{


uint32_t i,j;



UARTInit(115200);

//
初始化UART


ADCInit( ADC_CLK );


//
初始化 ADC


while(1)



{



ADCRead( 0 );


//
读取0通道转换值


while ( !ADCIntDone );

//
等待读取完成


ADCIntDone = 0;
//
清除读取完成标志


UARTBuffer[0]=(uint8_t)(*ADCValue>>8);

//
分离高2位数据


UARTBuffer[1]=(uint8_t)(*ADCValue);


//
分离低8位数据


UARTSend((uint8_t *)UARTBuffer, 2);

//
UART发送数据


for(i=0;i<5000;i++)


//
延时



for(j=0;j<1000;j++);



}


}



大家应该在几个8位单片机上都设计过这种ADC转换程序,相信大家也肯定经历过这个基础的过程。




UART的初始化在上一节已经详述了,我们直接来看看ADC的初始化ADCInit():

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

** Function name:
ADCInit


** Descriptions:
initialize ADC channel


** parameters:
ADC clock rate


** Returned value:
None


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

void ADCInit( uint32_t ADC_Clk )

{


LPC_SYSCON->;PDRUNCFG &= ~(0x1<<4);
//
打开ADC供电


LPC_SYSCON->SYSAHBCLKCTRL |= (1<<13);//
开启ADCAHB通道


LPC_IOCON->JTAG_TDI_PIO0_11 = 0x02;
//
选择ADC0引脚功能

/*设置ADC工作速率*/


LPC_ADC->CR = ((SystemCoreClock/LPC_SYSCON->SYSAHBCLKDIV)/ADC_Clk-1)<<8;


#if ADC_INTERRUPT_FLAG
//
使用ADC中断功能


NVIC_EnableIRQ(ADC_IRQn);



LPC_ADC->INTEN = 0x1FF;
/*
使能所有通道中断*/

#endif


return;


}

1、该函数唯一的参数填入欲设置的ADC工作速率,单位是Hz,本次实验填入4500000,即4.5MHz

2ADC初始化不同于前面其他设备的一个地方,在于它的电源是默认关闭的,所以首先要打开它的电源,明显是一个降低功耗的措施;

3ADC的工作速率,从用户手册可以查看到如下描述:

The APB clock (PCLK) is divided by CLKDIV +1 to produce the clock for the ADC, which should be less than or equal to 4.5 MHz.由此我们知道该ADC的最大驱动时钟频率是4.5MHz。同时分频数是CLKDIV +1,所以程序中要“ADC_Clk-1,至于为什么要左移8位呢?那是因为设置的分频数是存放于ADCCR(ADC控制寄存器)中的第8:15位。

4、如果使用中断功能,则除了要设置NVIC控制器之外,还需要在ADCINTEN中打开各个通道的中断功能,当某通道转换完成时,会触发对于中断。

初始化完毕之后,就可以开启ADC进行转换了。首先是ADCRead():



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

** Function name:
ADCRead


** Descriptions:
Read ADC channel


** parameters:
Channel number


** Returned value:
Value read, if interrupt driven, return channel #


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

uint32_t ADCRead( uint8_t channelNum )

{


if ( channelNum >= ADC_NUM )//
判断通道号是否过大


{



channelNum = 0;//
复位通道号


}



LPC_ADC->CR &= 0xFFFFFF00; //
清除所有转换通道的上一次选择状态


LPC_ADC->CR |= (1 << 24) | (1 << channelNum);//
选择通道,并开启转换     


return ( channelNum );//
返回通道号

}

1、本函数要求填入的唯一参数是所希望进行ADC的通道号;

2、转换前应该清除上一次通道被选状态;

3、在CR控制器中的26:24位控制着ADC的多种转换启动方式,本实验中使用最普通的一种:立即开始转换。

4、因为本实验使用了ADC的中断功能,转换结果在ADC中断中存储,所以此函数在使用ADC中断功能的情况下返回的是转换的通道号。

使用特权

评论回复
54
LPC300|  楼主 | 2010-6-28 23:02 | 只看该作者
ADC中断功能的情况下返回的是转换的通道号。




所以当本函数运行结束之后,ADC转换开始,等待进入中断服务函数:

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

** Function name:
ADC_IRQHandler


** Descriptions:
ADC interrupt handler


** parameters:
None


** Returned value:
None


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

void ADC_IRQHandler (void)

{


uint32_t regVal;



LPC_ADC->CR &= 0xF8FFFFFF;
/*
停止AD转换 */


regVal = LPC_ADC->STAT;
/*
通过读ADC状态寄存器来清除ADC中断标志 */


if ( regVal & 0x0000FF00 )

/*
首先检查有无溢出*/


{


         


regVal = (regVal & 0x0000FF00) >> 0x08;//
如果有,确认通道


switch ( regVal )//
通过读取转换值来清除ADC数据,注意这部分是错误数据,是丢弃的


{




case 0x01:regVal = LPC_ADC->DR0;break;




case 0x02:regVal = LPC_ADC->DR1;break;




case 0x04:regVal = LPC_ADC->DR2;break;




case 0x08:regVal = LPC_ADC->DR3;break;




case 0x10:regVal = LPC_ADC->DR4;break;




case 0x20:regVal = LPC_ADC->DR5;break;




case 0x40:regVal = LPC_ADC->DR6;break;




case 0x80:regVal = LPC_ADC->DR7;break;




default:break;



}



LPC_ADC->CR &= 0xF8FFFFFF;
/* stop ADC now */



ADCIntDone = 1;



return;



}



if ( regVal & ADC_ADINT )//
判断是否有任何一个通道转换结束


{



switch ( regVal & 0xFF )
/*
判断具体通道号,并进入ADC读取 */


{




case 0x01:ADCValue[0] = ( LPC_ADC->DR0 >> 6 ) & 0x3FF;break;




case 0x02:ADCValue[1] = ( LPC_ADC->DR1 >> 6 ) & 0x3FF;break;




case 0x04:ADCValue[2] = ( LPC_ADC->DR2 >> 6 ) & 0x3FF;break;




case 0x08:ADCValue[3] = ( LPC_ADC->DR3 >> 6 ) & 0x3FF;break;




case 0x10:ADCValue[4] = ( LPC_ADC->DR4 >> 6 ) & 0x3FF;break;




case 0x20:ADCValue[5] = ( LPC_ADC->DR5 >> 6 ) & 0x3FF;break;




case 0x40:ADCValue[6] = ( LPC_ADC->DR6 >> 6 ) & 0x3FF;break;




case 0x80:ADCValue[7] = ( LPC_ADC->DR7 >> 6 ) & 0x3FF;break;




default:break;



}



ADCIntDone = 1;//
读取结束标志


}



return;


}

1、进入中断服务程序之后,首先停止AD转换;

2、和UART一样,ADC中断标志也是通过读取来清除的;

3、首先要检查溢出错误,如果有,则数据无效,要通道读取来清除ADC转换数据寄存器(ADCDR);

4ADC中断有两种,一种是任何一个通道完成转换都会触发,一种是某个中断完成转换就会触发,本实验中两种中断都打开了,因此先判断是否有转换完成,再判断是哪个通道完成转换;

中断函数的结束意味着读取完成,剩下的就是将读出数据发送到UART去显示了。但在这之前,因为LPC1343ADC默认情况下是10位精度,而我们的UART是以字符为数据长度发送的,所以笔者特意将转换结果转换成了16位长度,分两次发送。现将本次实验运行过程概况如下:

UART初始化——ADC初始化——开始转换——转换结束触发中断——判断有无错误——有错误则放弃无效数据,无错误则读出有效数据——数据处理——发至UART

附上运行结果jpg两张,第一张,0通道引脚接在GND





截图01.jpg (29.53 KB)
2010-6-18 07:39








第二张,0通道接在VCC 3.3





截图02.jpg (32.04 KB)
2010-6-18 07:39









理论上3.3V为满赋值,转换结果应该是是11 1111 1111=0x3ff,不过实际并非如此,说明其实我们板子上引出的电源还是有一定波动的。

使用特权

评论回复
55
LPC300|  楼主 | 2010-6-28 23:02 | 只看该作者
第十二节:IIC总线接口
首先需要说明的是,本节内容只是讲述LPC1343内部IIC总线控制器的配置和使用,而不是对IIC总线协议进行剖析。所以不了解IIC总线协议的朋友,还需要事先去阅读PhillipsIIC-Bus或者ZLG的译文版。最好可以自己用普通的IO写一个软件IIC底层驱动,这样理解的效果是最好的。




本次实验设计也很常见,使用LPC1343的内部IIC总线控制器实现与IIC EEPROM的通讯。首先往EEPROM的某地址写入一个数据,然后再从该地址读取一个数据,最后互相比较,验证本次实验的正确性。




本次实验涉及到IIC-EEPROM协议中两个最基本也最常用的时序,一是指定地址写,二是指定地址读(此外IIC-EEPROM还有随机地址读,连续地址读……)。




我们还是以程序作为切入点,看看LPC1343IIC-BUS Controller的工作过程吧。先来看看带注释的主程序:

int main (void)

{


uint32_t i;



if ( I2CInit( (uint32_t)I2CMASTER ) == FALSE )
/*
初始化I2C */


{



while ( 1 );



}



I2CWriteLength = 3;//
写入长度为3个字节


I2CReadLength = 0;



I2CMasterBuffer[0] = PCF8594_ADDR;//
器件地址+“写”命令,经查询为0xA0


I2CMasterBuffer[1] = 0x00;//
器件内部指定存储地址         


I2CMasterBuffer[2] = 0x55;//
欲写入数据      


I2CEngine();//
启动I2C传输

  


for ( i = 0; i < 0x200000; i++ );
//
非常关键的延时!




for ( i = 0; i < BUFSIZE; i++ )



{



I2CSlaveBuffer = 0x00;//
清空接收缓冲


}



I2CWriteLength = 2;//
写入2个字节


I2CReadLength = 1;
读出1个字节


I2CMasterBuffer[0] = PCF8594_ADDR; //
器件地址,经查询为0xA0


I2CMasterBuffer[1] = 0x00; //
器件内部指定存储地址               


I2CMasterBuffer[2] = PCF8594_ADDR | RD_BIT;
//
器件地址+“读”命令,为0xA1


I2CEngine();//
启动I2C传输


return 0;


}



1、
主函数的执行流程是:

C初始化设置—配置写时序信息——启动IIC——配置读时序信息——启动IIC




2、
负责信息配置的I2CMasterBuffe数组里,可以看出在写时序配置中,I2CMasterBuffer[0]存放的是EEPROM的硬件地址和写命令,I2CMasterBuffer[1]存放的是用户指定的EEPROM内部存储地址,I2CMasterBuffer[2]存放的是欲写入的数据;

3、
负责信息配置的I2CMasterBuffe数组里,可以看出在读时序配置中,I2CMasterBuffer[0]存放的是EEPROM的硬件地址和写命令,I2CMasterBuffer[1]存放的是用户指定的EEPROM内部存储地址,I2CMasterBuffer[2]存放的是EEPROM的硬件地址和读命令;

4、
23点再看两遍……

首先我们来看初始化的过程:

uint32_t I2CInit( uint32_t I2cMode )

{


LPC_SYSCON->;PRESETCTRL |= (0x1<<1);//
软件复位IIC


LPC_SYSCON->SYSAHBCLKCTRL |= (1<<5);//
打开AHB时钟


LPC_IOCON->;PIO0_4 &= ~0x3F;
/* IIC
IO配置 */


LPC_IOCON->;PIO0_4 |= 0x01;
/* SCL
线 */


LPC_IOCON->;PIO0_5 &= ~0x3F;



LPC_IOCON->;PIO0_5 |= 0x01;
/* SDA
线 */


/*
清除一系列中断标志*/


LPC_I2C->CONCLR = I2CONCLR_AAC | I2CONCLR_SIC | I2CONCLR_STAC | I2CONCLR_I2ENC;



/*-
复位SCL线高低电平计数寄存器*/


LPC_I2C->SCLL
= I2SCLL_SCLL;



LPC_I2C->SCLH
= I2SCLH_SCLH;



/*
使能IIC中断 */


NVIC_EnableIRQ(I2C_IRQn);





LPC_I2C->CONSET = I2CONSET_I2EN;//
启用IIC接口


return( TRUE );//
初始化成功,返回TURE

}

这个初始化函数很简单,都只是对一些寄存器位的置位和清除而没有一些十分复杂或者互相交错起来的配置,

这些位的作用只要一查用户手册就明白了。




初始化完毕,配置好相应信息之后,启动IIC传输I2CEngine():

uint32_t I2CEngine( void )

{


I2CMasterState = I2C_IDLE;



RdIndex = 0;



WrIndex = 0;



if ( I2CStart() != TRUE )//
调用初始化函数,返回TURE表示成功


{



I2CStop();//
失败则发送结束信号


return ( FALSE );//
返回错误信息


}





while ( 1 )//
直到返回数据非应答信息才会退出此while循环,记住此处!!


{



if ( I2CMasterState == DATA_NACK )



{




I2CStop();




break;



}



}



return ( TRUE );


}



这个函数同样很简单,调用初始化以后,就等待传输完毕,根据传输的应答结果判断是否传输成功,给出返回数据;




还有两个小函数:I2CStart()I2CStop()。他们的作用看一下函数名字就知道了。

uint32_t I2CStart( void )

{


uint32_t timeout = 0;



uint32_t retVal = FALSE;



LPC_I2C->CONSET = I2CONSET_STA;
/*
置位起动标志位*/

   


/*--- Wait until START transmitted ---*/



while( 1 )



{



if ( I2CMasterState == I2C_STARTED )//
判断是否启动完成


{




retVal = TRUE;




break;



}



if ( timeout >= MAX_TIMEOUT )//
判断是否启动超时


{




retVal = FALSE;




break;



}



timeout++;



}



return( retVal );


}



只要往LPC_I2C->CONSET 写入I2CONSET_STAIIC总线控制器就自动开始发送起始信号了,之后判断是否启动成功,是否启动超时,也是比较简单的。




同理I2CStop(),只要写入LPC_I2C->CONSET = I2CONSET_STO,剩下的就交给IIC控制器来完成了,这也是硬件IIC的一大好处:

uint32_t I2CStop( void )

{


LPC_I2C->CONSET = I2CONSET_STO;
/*
设置结束标志 */


LPC_I2C->CONCLR = I2CONCLR_SIC;

/*
清除IIC中断标志*/


while( LPC_I2C->CONSET & I2CONSET_STO ); /*
等待结束完成*/


return TRUE;


}


至此可以发现,无论是起始函数还是结束函数,都是通过一些变量来判断IIC的进程的,那么这些变量都在哪里被操作呢?

使用特权

评论回复
56
LPC300|  楼主 | 2010-6-28 23:03 | 只看该作者
很容易就想到了,是中断服务函数,这也是本节要着重讲述的地方:



void I2C_IRQHandler(void)



{



  uint8_t StatValue;







  /* this handler deals with master read and master write only */



  StatValue = LPC_I2C->STAT;



  switch ( StatValue )



  {



       case 0x08:                    /* A Start condition is issued. */



       WrIndex = 0;



       LPC_I2C->DAT = I2CMasterBuffer[WrIndex++];      //起始信号发送成功,发送"器件地址



+写"命令



       LPC_I2C->CONCLR = (I2CONCLR_SIC | I2CONCLR_STAC);//读取以清除



       I2CMasterState = I2C_STARTED;//改变IIC状态标志变量,启动完成



       break;



      



       case 0x10:                    /* 重复启动成功 */



       RdIndex = 0;



       /* 发送器件地址+读命令 */



       LPC_I2C->DAT = I2CMasterBuffer[WrIndex++];



       LPC_I2C->CONCLR = (I2CONCLR_SIC | I2CONCLR_STAC); //读取以清除



       I2CMasterState = I2C_RESTARTED; //改变IIC状态标志变量,重复启动完成



       break;



      



       case 0x18:                    //写入器件地址成功,收到应答,发送数据(存储地址)



       if ( I2CMasterState == I2C_STARTED )//判断IIC进程



       {



         LPC_I2C->DAT = I2CMasterBuffer[WrIndex++];



         I2CMasterState = DATA_ACK;



       }



       LPC_I2C->CONCLR = I2CONCLR_SIC; //读取以清除



       break;



      



       case 0x28:      //发送数据成功,收到应答,无操作(中断不清除)



       case 0x30://再次进入中断服务,因为上一个case没有发送数据,所以无应答



       if ( WrIndex < I2CWriteLength )//判断将要写入的数据



       {   



         LPC_I2C->DAT = I2CMasterBuffer[WrIndex++]; //写入数据



         I2CMasterState = DATA_ACK;



       }



       else//产生重复起始标志,准备进入读时序



       {



         if ( I2CReadLength != 0 )



         {



              LPC_I2C->CONSET = I2CONSET_STA;    /* Set Repeated-start flag */



              I2CMasterState = I2C_REPEATED_START;



         }



         else//不写也不读,则发送结束信号



         {



              I2CMasterState = DATA_NACK;



              LPC_I2C->CONSET = I2CONSET_STO;      /* Set Stop flag */



         }



       }



       LPC_I2C->CONCLR = I2CONCLR_SIC;



       break;



      



       case 0x40:      /*主机进入读时序,读命令发出*/



//由I2CReadLength变量判断是单字节读还是连续读,若是单字节读则主机不需要对从器件



//应答,本处是单字节读,



    if ( I2CReadLength == 1 )



       {



         /* 单字节读,不应答 */



         LPC_I2C->CONCLR = I2CONCLR_AAC;      /* assert NACK after data is received */



       }



       else



       {



         /*多字节读,应答 */



         LPC_I2C->CONSET = I2CONSET_AA;  /* assert ACK after data is received */



       }



       LPC_I2C->CONCLR = I2CONCLR_SIC;



       break;



      



       case 0x50:      /* 收到字节,主机给出应答 */



       I2CSlaveBuffer[RdIndex++] = LPC_I2C->DAT;



       if ( RdIndex < I2CReadLength )



       {   



         I2CMasterState = DATA_ACK;



         LPC_I2C->CONSET = I2CONSET_AA;  /* assert ACK after data is received */



       }



       else



       {



         I2CMasterState = DATA_NACK;



         LPC_I2C->CONCLR = I2CONCLR_AAC;      /* assert NACK on last byte */



       }



       LPC_I2C->CONCLR = I2CONCLR_SIC;



       break;



      



       case 0x58://收到字节,主机不给出应答,而发送结束信号结束本次IIC通讯



       I2CSlaveBuffer[RdIndex++] = LPC_I2C->DAT;



       I2CMasterState = DATA_NACK;



       LPC_I2C->CONSET = I2CONSET_STO;   /* Set Stop flag */



       LPC_I2C->CONCLR = I2CONCLR_SIC;    /* Clear SI flag */



       break;







       case 0x20:             /* regardless, it's a NACK */



       case 0x48:



       LPC_I2C->CONCLR = I2CONCLR_SIC;



       I2CMasterState = DATA_NACK;



       break;



      



       case 0x38:             /* Arbitration lost, in this example, we don't



                                   deal with multiple master situation */



       default:



       LPC_I2C->CONCLR = I2CONCLR_SIC;   



       break;



  }



  return;



}

使用特权

评论回复
57
LPC300|  楼主 | 2010-6-28 23:03 | 只看该作者
这个函数比较长,而IIC通讯时序本身情况比较多,前文已经提到,本节只讲述单字节读和单字节写过程,那么现在将这个两个过程和这个中断服务函数对应起来,先是写过程:

1、
发送起始信号成功,从而产生中断0x08,并写入器件硬件地址+写命令,并清除IIC中断。本步骤完成时序“起始——写器件地址+命令”;

2、
写入器件地址+写命令成功,成功收到应答,从而产生0x18中断,写入器件存储地址信息,并清除IIC中断。本步骤完成时序“应答——写存储地址”;

3、
写入器件存储地址成功,成功收到应答,从而产生0x30中断,经判断写入数据。本步骤完成时序“应答——写数据”,

4、
写入数据成功,成功收到应答,从而产生0x30中断,此时经过判断选择产生结束信号本步骤完成时序“应答——结束信号”。

概括完成的时序“起始—>写器件地址+写命令—>应答—>写存储地址—>应答—>写数据—>应答—>结束信号”

就是一个很标准的IIC写时序了:




1.jpg (14.4 KB)
2010-6-21 14:56






接下来就是读时序:

1、
发送起始信号成功,从而产生中断0x08,并写入器件硬件地址+写命令,并清除IIC中断。本步骤完成时序“起始——写器件地址+命令”;

2、
写入器件地址+写命令成功,成功收到应答,从而产生0x18中断,写入器件存储地址信息,并清除IIC中断。本步骤完成时序“应答——写存储地址”;

3、
写入器件存储地址成功,成功收到应答,从而产生0x30中断,经判断产生。本步骤完成时序“应答——重复起始”;

4、
重复起始成功,从而产生0x10中断,写入器件地址+读命令,清除IIC中断。本步骤完成“写入器件地址+读命令”

5、
写入器件地址+读命令成功,并受到应答,从而产生0x40中断,选择是否对从机产生应答,本处因为是单字节读,所有不应答。本步骤完成“应答”;

6、
接收数据成功,产生0x58 中断,同时根据第5点不对从机应答。最后产生结束信号。本步骤完成“接收数据——非应答——结束”

再概括一下:“起始—>写器件地址+写命令—>应答—>写存储地址—>应答—>重复起始—>写入器件地址+读命令—>应答—>接收数据—>(主机)非应答—>结束”

同样是一个很标准的IIC随机字节读时序:







2.jpg (20.95 KB)
2010-6-21 14:56

  

在中断函数完成这两个过程之后,会分别对I2CMasterState给出DATA_NACK或者DATA_ACK,从而得以在I2CEngine函数里面返回是否通讯成功的信息。

因为该LPC1343评估板并没有EEPROM外设,所以这里使用了AT24C02作为本次实验外设,调入LPC1343 examplei2c实例,我们要将其改造为单字节读和单字节写功能,所以将主函数修改如本**起始处,编译运行。可以查看到读出数据为0x55,和写入的数据一致。实验完结。







下节为本系列终章,SPI Bus… …

使用特权

评论回复
58
huzixian| | 2010-6-28 23:04 | 只看该作者
写这样的技术**确实很累,需要很多的积累。楼主辛苦了!

使用特权

评论回复
59
6019实验室| | 2010-6-28 23:05 | 只看该作者
楼主,真是前辈高人,在如此短的时间内,搞掂LPC1343的功能,向您学习了

使用特权

评论回复
60
思行合一| | 2010-6-28 23:05 | 只看该作者
同感啊,好贴,LZ辛苦。



好多人都没时间挨个帖子翻的

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则