MPLAB-IDE-C语言编程代码实例-分析

以下学习笔记均使用C语言编程,编程软件为MPLAB IDEV8.89附加PICC18V9.66PRO版本。调试单片机采用pic18f14k22单片机。

  1. CONFIG
    __CONFIG();此段代码为PIC配置(详情在手册260页),通过网络了解为硬件资源的开启与关闭,我的理解是与AVR单片机的熔丝位等设置相同。要想使此函数必须包含“pic18.h”与“pic18f14k22.h”这两个头文件。手册259页
  2. IO控制
    代码如下:
    #include<pic18.h>
    #include<pic18f14k22.h>
    __CONFIG(1,FOSC_HS) ; //高速晶振模式
    __CONFIG(2,PWRTEN_ON & WDTPS_128 & WDTEN_OFF ) ; //上电延迟开,看门狗128分频,看门狗关
    __CONFIG(4,STVREN_ON) ;//堆栈溢出复位
    void delay();
    void main()
    {
    TRISA0=0; //PORTA输出
    while(1)
    {
    RA0=0;
    delay();
    RA0=1;
    delay();
    }
    }
    void delay()
    {
    int i;
    for(i=10000;i>0;i–);
    }
    以上代码,亲测可以实现18f14k22单片机的RA0 IO的电平跳转。晶振用的10MHZ。delay延迟大概52mm左右。PORTA寄存器内容见“PIC18F14K22中文资料”第81页。
  3. Uart控制
    代码如下:
    注意:这里我省略了头文件和CONFIG配置部分,具体配置和上述一致。
    void interrupt isr(void); //PICC可以实现C 语言的中断服务程序。中断服务程序有一个特殊的定义方法:
    其中的函数名“ISR ”可以改成任意合法的字母或数字组合,但其入口参数和fa返回参数类型必须是“void ”型,亦即没有入口参数和返回参数,且中间必须有一个关键词“interrupt ”。
    void interrupt isr(void)//这里是中断接收函数,若还有其他中断均写在这里。
    {
    if (RCIE && RCIF)
    {
    RC_DATA=RCREG;
    flag=1;
    }
    }
    void USART_init()
    {
    SPEN=1;//串行端口使能
    TRISB7=0;//RB7 TX设置成输出
    TRISB5=1;//RB5 RX设置成输入
    ANS11=0; //RB5有模拟输入功能所以此处要关闭
    SPBRGH=0x00;
    SPBRG=0x40;//10MHZ晶振设定9600波特率 n=64.1 换算10进制 64=0x40
    BRGH=1; //高速
    BRG16=0;
    SYNC=0; //8位异步,EUSART模式,波特率=FOSC/[16(n+1)] 页189手册
    TX9=0;//选择8位发送数据
    // TX9D=0;//暂时作为基偶校验
    TXEN=1;//发送使能
    RX9=0;//选择8位接收
    RCIE=1;//允许接收中断
    PEIE=1;//允许外设中断
    CREN=1;//接收使能

GIE=1;//低优先级中断,使能全局中断 手册67页
}
void main()
{
unsigned char i;
TRISA0=0; //PORTA输出
USART_init(); //串口初始化
while(1)
{
if(flag1)
{
TX_data(RC_DATA);
RA0=1;
delay(1000);
RA0=0;
delay(1000);
i++;
if(i
255){i=0;}
flag=0;
}
}
}
void delay(unsigned int time)
{
unsigned int i,m;
for(m=time;m>0;m–)
{
for(i=357;i>0;i–);
}
}
void TX_data(unsigned char Data)
{
TXREG=Data;
while(TRMT==0);
}
上述程序功能是:当用串口调试助手发送16进制数时,PIC返
回相同16进制数值。同时RA0会有一次信号跳转。
串口使能函数:USART_init()下面就寄存器逐个分析。每条代码的意义已经在上述代码中进行了注释,这里就不在赘述了。
这里的串口使能,采用的是中断接收,异步收发模式。
PIC的EUSART发送器需要从以下三个控制位控制(PIC18F14K22中文手册第179页有详细解释)

在18f14k22单片机中TX(RB7)和RX(RB5)只有一处,所以只需要设置RB7为输出,RB5为数字输入即可:

波特率设置在手册的第189页:

上表可以看出目标波特率按照右方公式可以计算得到,上述代码中我采用的是8位异步模式所以:

若实现9600=FOSC/[16(n+1)] n≈64.1,这里取整为0x40
故 波特率寄存器SPBRG=0x40,SPBRGH=0x00。
EUSART接收器需要配置以下三个控制位(手册182页):

一般情况下串口的收发均采用8位数据,所以这里TX9,RX9设定为0。
PIC中断逻辑如下图(手册66页)

接收中断属于外设中断,相应的操作如手册所示就不在赘述了。
中断子函数void interrupt isr(void),这里得注意,PICC编程器的中断需要“interrupt”为中间关键字,至于后面的名称可以字母或者数字下划线取代。
接收中断中的处理只需要关注RCIF即可。

发送处理只需要关注发送移位寄存器状态位手册186页:

  1. ADC模数转换
    ADC转换步骤可以见手册205页,测试代码如下(省略头文件和相应配置):
    /******************
    函数申明
    ******************/
    void delay(unsigned int time);
    void USART_init();
    void ADC_init();
    unsigned int ADC_Get();
    void TX_data(unsigned char Data);
    void interrupt isr(void);//
    /**********************若想要将interrept.h自建头文件加入工程
    *名 称:USART_init() 需要在“build option” 中Directories中
    *功 能:串口使能函数 show Directories for中修改然后查找添加.h文件
    *入口参数:
    *出口参数:
    *说 明:
    */
    void USART_init()
    {
    SPEN=1;//串行端口使能
    TRISB7=0;//RB7 TX设置成输出
    TRISB5=1;//RB5 RX设置成输入
    ANS11=0; //RB5有模拟输入功能所以此处要关闭 很重要
    SPBRGH=0x00;
    SPBRG=0x40;//10MHZ晶振设定9600波特率 n=64.1 换算10进制 64=0x40
    BRGH=1; //高速
    BRG16=0;
    SYNC=0; //8位异步,EUSART模式,波特率=FOSC/[16(n+1)] 页189手册
    TX9=0;//选择8位发送数据
    // TX9D=0;//暂时作为基偶校验
    TXEN=1;//发送使能
    RX9=0;//选择8位接收
    RCIE=1;//允许接收中断
    PEIE=1;//允许外设中断
    CREN=1;//接收使能
    GIE=1;//使能全局中断 手册67页
    }
    /

    *名 称:ADC_init()
    *功 能:AD使能函数
    *入口参数:
    *出口参数:
    *说 明:
    /
    void ADC_init()
    {
    TRISC1=1;//设置RC1为ADC输入
    ANS5=1;// 模拟输入
    CHS0=1;
    CHS1=0;
    CHS2=1;
    CHS3=0;//211页 AN5启用ADC5输入
    ADCON1=0x00;//取内部VDD为参考电压
    ADCON2=0x92;//右对齐,4Tad,32分频
    // ADFM=1;//右对齐
    // ADCS0=0;
    // ADCS1=1;
    // ADCS2=0; //外部晶振32分频 //000=FOSC/2 001=FOSC/8 010=FOSC/32
    ADON=1;//使能ADC
    ADIE=1;//允许AD中断
    }
    /

    *名 称:ADC_Get()
    *功 能:转换AD数据
    *入口参数:
    *出口参数:uint型AD值
    *说 明:
    *********************/
    unsigned int ADC_Get()
    {
    unsigned int AD_value;
    DONE=1;//开始AD转换
    while(DONE);
    AD_value=ADRESH;
    AD_value=AD_value
    256+ADRESL;
    return AD_value;
    }
    /**中断程序部分
    ****************************/

void interrupt isr(void)
{
if(RCIE && RCIF)//接收中断部分
{
RC_DATA=RCREG;
flag=1;
}

else if(ADIE&&ADIF)//AD中断部分
{
ADIF=0;//手册上显示需要软件清0
AD_flag=1;
}

}
/**********************
*名 称:main()
*功 能:主函数
*入口参数:
*出口参数:
*说 明:
*/
void main()
{
unsigned char i,ADH,ADL;
TRISA0=0; //PORTA输出
TRISC5=0; //RC5为输出
USART_init(); //串口初始化
ADC_init();//AD初始化
while(1)
{
if(flag)
{
ADH=ADC_Get()/256;
ADL=ADC_Get()%256;
TX_data(ADH);
TX_data(ADL);
flag=0;
}
if(AD_flag)
{
AD_flag=0;
RC5=1;
delay(10);
RC5=0;
}
RA0=1;
delay(20);
RA0=0;
delay(20);
}
}
/

*名 称:delay()
*功 能:延时函数
*入口参数:uint
*出口参数:
*说 明:延时单位ms,10M晶振
*/
void delay(unsigned int time)
{
unsigned int i,m;
for(m=time;m>0;m–)
{
for(i=357;i>0;i–);
}
}
/

*名 称:TX_data(uchar Data)
*功 能:串口使能函数
*入口参数:uchar
*出口参数:
*说 明:发送子函数
***********************/
void TX_data(unsigned char Data)
{
TXREG=Data;
while(TRMT==0);
}
上述代码经过我的验证,可以实现串口发送一个16进制,可以返回2个字符的AD转换结果。在返回结果的同时RC5电平会有一次跳变。
(过程中我发现用手去触摸AD输入口RC1会出现死机,我猜测可能是我的人体静电导致AD如入结果溢出导致单片机复位,此猜测后续需要验证)。
下面我将对AD配置进行详细的介绍:
ADC_init();使能函数中的内容均根据手册描述配置,在手册206页可以明确ADC配置:

这里我使用的RC1口作为AD模拟量采集口,查看IO信息可以知道RC1同时也对应这AN5,这在AD设置中至关重要。
配置寄存器主要与ADCON0,ADCON1,ADCON2有关。
ADCON0主要与IO配置,AD状态,AD使能有关,如下表

当 TRISC1=1;//设置RC1为ADC输入
ANS5=1;// 模拟输入
ANS5这表示以模拟量输入,前面有过介绍
CHS0=1;
CHS1=0;
CHS2=1;
CHS3=0;
这三个就表示打开AD模拟通道选择,我这里0101表示打开AN5。

GO/DONE这是AD转换的状态位,在PICC18库文件中GO/DONE用DONE表示。

ADCON1:与参考电压有关

具体的配置可以看手册来选择基准电压。
在程序中我使用的是内部VDD作为参考基准电压正,VSS作为基准电压负值。
ADCON2:与AD转换结果对齐方式,AD采样时间,AD采样频率有关。

程序中我采用的是右对齐,4Tad,32分频。
PIC18F14K22,AD结果是10bit,意味着数字结果00H~03FFH。

转换的结果存储在ADRESH,ADRESL中。
右对齐的计算为:
AD_value=ADRESH;
AD_value=AD_value*256+ADRESL;
这个是进制转换需要自己理解。
5. ADC中断
ADC中断在的配置在手册210页,A/D转换步骤中也有提到:

在上述代码中我将AD中断的内容写在void interrupt isr(void)
子函数下。这种写在一起的方式是PICC特有的一种模式。手册中提到,PIC18有高级中断与低级中断。MCC中好像可以通过写asm来区分开高级与低级中断。我尝试了一下,在MPLABIDE中用_asm定义向量0x08一直编译出错。通过查询库文件我的理解是PICC将高级与低级融合在一起,仅识别interrupt这个关键字。在程序中我并没有开启中断优先级IPEN。结果调试还算顺利,暂时不去深究。
通过了解,当没有定义中断优先级,那么interrupt xxxx命名的中断需要在程序中判断IE与IF。所以程序中两种中断处理采用上述写法:
if(RCIE && RCIF)//接收中断部分
{
RC_DATA=RCREG;
flag=1;
}

else if(ADIE&&ADIF)//AD中断部分
{
ADIF=0;//手册上显示需要软件清0
AD_flag=1;
}
AD中断的步骤,简单来说就是AD转换结束后就执行一次中断,所以程序中,通过串口发送指令,当接收中断接收到字符,就出发main()中的1次AD转换。
注意DONE=1是决定AD转换开始的前提。
6. 定时器0
PIC18单片机的定时器一共有4个,Time0-3,可用作定时器或者计数器完成一些对时间有要求的工作,这里我就不多说了。简单的理解定时器就是独立主程序之外的小程序。
这里从Time0开始介绍,程序如下:
#include<pic18.h>
#include<pic18f14k22.h>//若写了PIC18.h头文件 此段可省略
__CONFIG(1,FOSC_HS) ; //高速晶振模式
__CONFIG(2,PWRTEN_ON & WDTPS_128 & WDTEN_OFF ) ;//上电延时开,看门狗分频128,看门狗关闭
__CONFIG(4,STVREN_ON) ;//堆栈溢出复位
/******************
函数申明
******************/
void Time0_init();
void Time0_init()//98页
{
// T0CON:TIMER0控制器
TMR0ON=1;//使能Time0开
T08BIT=0;//配置8位定时器或者16位定时器,这里配置为16位
T0CS=0;//配置内部时钟
T0SE=0;//低电平到高计数
PSA=0;//分频来自分频器
T0PS2=0;
T0PS1=0;
T0PS0=0;//000 1:2预分频比 001 1:4预分频比 手册97页
TMR0IE=1;//使能TIM0中断
TMR0IF=0;//Time0中断溢出标志位
TMR0=0x0000; //用了10M的外部晶振,这里16位65535个计数值则总时间=65535/(10000000/4/2)≈52.4ms
}
void interrupt isr(void)
{
if(TMR0IE&&TMR0IF)//定时器0溢出中断
{
TMR0=0x0000;
TMR0IF=0;//需要软件清0
LATA0=~LATA0;
}

void main()
{
TRISA0=0; //PORTA输出
Time0_init();//TIME0初始化
GIE=1;//使能全局中断
while(1);
}
以上程序是我截取的有关Time0部分的,直接复制应该不会有什么问题,重点是理解它。
结合手册,Time0的介绍在98页:

void Time0_init()使能函数里面的各种设置均按照寄存器标注的来设置,这里不用赘述了。我在代码的后面也做了注释,应该没有什么问题。
就定时时间上,我来稍微介绍一下。

上图是Time0的设置和工作流程图,在手册的99页。配置部分我们不看,主要在时钟上的计算。程序中,我们设定定时器为16位工作模式:0000h-FFFFh,也就是计数从0-65535,当计数满引发TMR0IF标志位置1,这里就可以触发Time0的溢出中断。
当T0CS为0,选择内部时钟FOSC/4,预分频为1:2,也就是Time0计数一次所需要的时间t=1/(10MHZ/4/2)=0.8us,那么计数从0-65535需要65536次为0.8*65536≈52ms。
定时器0的中断和AD中断其实都是大同小义,手册67页

如上述中断寄存器,配置Time0溢出中断只需要使能TMR0IE位即可。
上述溢出中断程序:void interrupt isr(void)其实和上述串口,AD中断是一个子函数,我这里省略了其他内容而已。
当溢出中断使能,在计数值溢出时TMR0IF标志置1,则程序自动跳转到interrupt中,这里在之前也介绍过。

手册中标注,溢出中断标志位单片机不会自动清0,需要手动,这里切记。
每当time0计数器记满,理论上是从0重新开始,不过为了让程序必须按照既定的设定时间,在中断中必须重新给计数器赋值,已达到循环的目的。这也是定时器的初忠。
我上述的程序,若执行成功,可以通过示波器看到RA0 IO口的输出电平会有周期性变化。每进入一次溢出中断,RA0的电平就会反转。
LATA0=LATA0;这样的写法,有的同学可能会有疑问,为什么不写成RA0=RA0;这里你可以尝试以下,其实只针对RA0取反是无法达到电平取反的目的。这个与头文件里面的定义有关。我写的程序均按照PICC18 9.66,提供的标准头文件,面向的内容主要还是寄存器操作。寄存器操作可以加大自身对PIC的理解,这一点毋庸置疑。当最好的还是使用PICC18的标准库文件。这样可以大大节约开发时间。不过这也违背了我初学PIC的目的。打住,继续正文。。。
关于IO操作,之前的笔记我也简单的提到过这里,我再给大家介绍两种写法,这也可以加深对单片机的了解:
见手册79页:

IO的控制,主要依赖于上述三个寄存器。TRIS反向寄存器我就不解释了。要想使用IO端口必须使用这个寄存器。方向寄存器在我知道的单片机中,AVR的好像与PIC的类似,用法也差不多。51的我记得不是很清楚了,好多年不用了,好像没有涉及的方向寄存器。
就拿RA0举例,要想使RA0的电平置1则:
TRISA0=0;//定义输出
RA0=1;
PORTA=0x01;
LATA0=1;这三种写法均可以达到相同目的,实现IO电平置1。

结合手册中箭头所指示,要想实现对IO驱动值的读写须使用LAT寄存器。那么LATA0取反也就可以明晰了。
网络上有RA0=~RA0还有RA0=!RA0这样写的方式,可能是基于的软件的不同吧。我这里无法实现。这里我也不是很明白。
7. 定时器1
定时器1,2,3的使用方式和Time0其实没有多大区别。它的优势在于它可以配合ECCP模块实现很多特殊功能。这些功能是很多特色产品依赖的基本条件。
定时器1的实验代码如下:
void Time1_init()
{
TRISC5=0; //RC5为输出 RC5为CCP1输出管脚
RC5=1;
//T1CON:TIMER1控制寄存器101页
RD16=1;//1次16位操作读写
//T1RUN=1;//主系统时钟由Time1振荡器产生
T1CKPS0=1;
T1CKPS1=1;//00 =1:1分频01 =1:2分频10 =1:4分频11 =1:8分频
//T1OSCEN=1;//使能Time1振荡器
// T1SYNC=0;// 同步外部时钟
TMR1CS=0;// 内部时钟
TMR1ON=1;//使能Time1
TMR1IE=1;//允许Time1溢出中断
TMR1IF=0;//Time1溢出中断标志位
TMR1=0x0000;//计数从0开始大概在210ms左右

CCP1IF=0;//CCP1中断标志位
CCP1IE=1;//允许CCP1中断
CCPR1=0x00FF;//比较匹配数值
CCP1M3=1;
CCP1M2=0;
CCP1M1=1;
CCP1M0=0;//比较匹配模式 113页
PEIE=1;//CCP1与Time1中断属于外设中断,这里允许外设中断 72页
}
void interrupt isr(void)
{
if(TMR1IE&&TMR1IF)//定时器1溢出中断
{
TMR1=0x0000;
TMR1IF=0;//需要软件清0
//LATA0=~LATA0;
RC5=1;
}
else if(CCP1IE&&CCP1IF)//CCP1比较匹配中断
{
// CCPR1=0x00FF;
CCP1IF=0;//需软件清0
RC5=0;
}
}
void main()
{
TRISA0=0; //PORTA输出
TRISA1=0;
Time1_init();//Time1初始化
GIE=1;//使能全局中断
while(1)
{
// LATA1=~LATA1;
//delay(100);
}
}
上述代码为定时器1的使能和中断部分,运用了定时器1的溢出中断和ECCP模块的匹配中断模式。
程序所达到的效果就是让RC5输出固定周期的脉宽。可以实现类似于PWM控制舵机。
其实这里我仅仅作为一个实验,并未真实的用舵机操作。运用ECCP的PWM模式可以充分的发挥PWM功能的优势。
就程序本身进行简单的介绍:
Time1的配置要比time0要复杂一些,就101页手册来看:

程序中Time1的设置重点在于对Time1时钟源的理解,其他的与Time0无差别。

时钟源的理解需要从上图来解释,在手册的102页,Time1的时钟源可以选择外部时钟也可以用内部时钟。这里得注意,所谓的外部时钟不是指单片机工作用的晶振时钟HS,而是Time1需要使用外部时钟为LP,这里需要从手册上104页,理解它的时钟作用。
程序中我仅使用了内部时钟,所以TMR1CS=0,至于图中画圈的可以忽略。
为了方便理解,time1的计数器依旧选择采用int方式:0000h-FFFFh,所以RD16=1,手册101页可寻:

至于Time1溢出中断如手册70页,与72页

这里的中断和Time0几乎没有差别。只要关心一点,Time1溢出中断属于外设中断,使用中断必须打开PEIE=1;。
8. ECCP比较匹配功能
结合定时器1中介绍的内容,这里继续介绍ECCP模块的运用见手册113页。
ECCP模块主要有三个功能,PWM,捕捉,比较。PWM信号我就不多说了,网络资料很多,捕捉功能也很简单,就是捕捉信号脉宽的时间然后进行分析。可以做红外,通讯类的东西。比较功能是我代码中主要提到的,我这里用到比较匹配中断是为了实现自定义周期,自定义脉宽的功能,理论上与PWM功能类似,不过我这里倾向与理解。
若想使用ECCP的比较中断,那么首先就需要理解ECCP的比较功能。如下图手册116页

ECCP比较模式一般是配合着定时器1或者定时器3来使用。所以在比较源的选择需要理解。这里我们使用的time1,所以T3CCP1=0(程序中没有写这一段是因为只有在使用Time3作为比较源的时候才需要,这里仅保持默认就可以了),time1中的计数器TMR1的值与CCPR1的值不断进行对比。达到匹配后,若打开了CCP1IE中断,那么就会进入中断。至于比较匹配CCP1 IO的电平问题,可以在113页中看到。

我在程序中选择了如上功能,并没有用到CCP1 IO的功能。CCP1 IO也就是RC5。我这里有点画蛇添足。
CCP1比较匹配中断也属于外设中断,PEIE在设置time1时已经打开,所以这里无须再进行设置。
9. IIC操作
IIC串行接口是个非常常用的一种通讯方式,只需要两根线就可以达到通讯目的实现主机与从机的通讯。见手册135页主同步串行口MSSP模块。这个模块中包括两种通讯,SPI与IIC,两者通讯很接近,这里我重点介绍IIC,手册144页:

想要实现IIC功能,首先得知道IIC的工作原理,这里我就不说了,网络上有很多解释。其实在我看来不存在懂不懂,记住就可以了。而PIC的IIC通许用到的寄存器也就是我上面的手册截图,下方是我的代码:
#include<pic18.h>
#include<pic18f14k22.h>//若写了PIC18.h头文件 此段可省略
#define uchar unsigned char
#define uint unsigned int
uchar flag,RC_DATA;
__CONFIG(1,FOSC_HS) ; //高速晶振模式
__CONFIG(2,PWRTEN_ON & WDTPS_128 & WDTEN_OFF ) ;//上电延时开,看门狗分频128,看门狗关闭
__CONFIG(4,STVREN_ON) ;//堆栈溢出复位
/******************
函数申明
******************/
void delay(uint time);
void USART_init();
void TX_data(uchar Data);
void interrupt isr(void);
void IIC_init();
void IIC_write(uchar Data);
uint IIC_read(uchar Addr);

/**********************
*名 称:IIC_init()
*功 能:IIC使能函数
*入口参数:
*出口参数:
*说 明:
*/
void IIC_init() //手册144页
{
// SSPCON1=0x38;
SSPEN=1;//使能串口将SDA,SCL配置成串口引脚
CKP=1;//释放时钟
SSPM3=1;
SSPM2=0;
SSPM1=0;
SSPM0=0;//IIC主模式161页,时钟=FOSC/(4
(SSPADD+1))
TRISB4=1;//SDA
TRISB6=1;//SCL 148页
SSPSTAT=0x80;//标准高速模式145页
SSPADD=0x09;//波特率设置 SCL周期=[(ADD<7:0>+1)4]/FOSC
SSPCON2=0x00;
// SSPIF=0;//清SSP中断标志
// SSPEN=1;//SSP模块使能
}
/

*名 称:IIC_write()
*功 能:IIC发送函数
*入口参数:
*出口参数:
*说 明:
*/
void IIC_write(uchar Data)//手册168页
{
// uchar Addr=0xA0;
SSPIF=0;
SEN=1;//启动位
while(!SSPIF);
SSPIF=0;
SSPBUF=0xA0;//写操作指令
while(!SSPIF);
SSPIF=0;
SSPBUF=0x01;//写地址指令
while(!SSPIF);
SSPIF=0;
SSPBUF=Data;
while(!SSPIF);
SSPIF=0;
PEN=1;//停止位
while(!SSPIF);
SSPIF=0;
delay(1);
}
/

*名 称:IIC_read(uchar Addr)
*功 能:IIC发送函数
*入口参数:uchar Addr
*出口参数:uchar data
*说 明:
***********************/
uint IIC_read(uchar Addr)//手册169页
{
uint Data;
SSPIF=0;
SEN=1;//启动位
while(!SSPIF);
SSPIF=0;
SSPBUF=0xA0;//写指令
while(!SSPIF);
SSPIF=0;
SSPBUF=Addr;//写地址
while(!SSPIF);

SSPIF=0;
RSEN=1; //重新开始
while(!SSPIF);
SSPIF=0;
SSPBUF=(0xA0|0x01);//读指令
while(!SSPIF);
SSPIF=0;
RCEN=1;//使能接收
while(!SSPIF);
Data=SSPBUF;
while(!SSPIF);
SSPIF=0;
ACKEN=1;//使能应答位
while(!SSPIF);
SSPIF=0;
PEN=1;//停止位
while(!SSPIF);
SSPIF=0;
return Data;
}
/**********************
*名 称:USART_init()
*功 能:串口使能函数
*入口参数:
*出口参数:
*说 明:
***********************/
void USART_init()
{
SPEN=1;//串行端口使能
TRISB7=0;//RB7 TX设置成输出
TRISB5=1;//RB5 RX设置成输入
TRISC7=0;//RC7 TXEN端口配置成输出
ANS11=0; //RB5有模拟输入功能所以此处要关闭 很重要
SPBRGH=0x00;
SPBRG=0x40;//10MHZ晶振设定9600波特率 n=64.1 换算10进制 64=0x40
BRGH=1; //高速
BRG16=0;
SYNC=0; //8位异步,EUSART模式,波特率=FOSC/[16(n+1)] 页189手册
TX9=0;//选择8位发送数据
// TX9D=0;//暂时作为基偶校验
TXEN=1;//发送使能
RX9=0;//选择8位接收
RCIE=1;//允许接收中断
PEIE=1;//允许外设中断
CREN=1;//接收使能
// GIE=1;//使能全局中断 手册67页
}
/**中断程序部分
****************************/

void interrupt isr(void)
{
if(RCIE && RCIF)//接收中断部分
{
RC_DATA=RCREG;
flag=1;
}
}
/**********************
*名 称:main()
*功 能:主函数
*入口参数:
*出口参数:
*说 明:
***********************/
void main()
{
uchar m;
TRISA0=0; //PORTA输出
TRISA2=0;

USART_init(); //串口初始化
IIC_init();//IIC初始化
GIE=1;//使能全局中断
IIC_write(10);
while(1)
{
LATA2=~LATA2;
delay(10);
if(flag)
{
flag=0;
m=IIC_read(1);
TX_data(m);
}

}
}
/**********************
*名 称:delay()
*功 能:延时函数
*入口参数:uint
*出口参数:
*说 明:延时单位ms,10M晶振
*/
void delay(unsigned int time)
{
unsigned int i,m;
for(m=time;m>0;m–)
{
for(i=357;i>0;i–);
}
}
/

*名 称:TX_data(uchar Data)
*功 能:串口使能函数
*入口参数:uchar
*出口参数:
*说 明:发送子函数
***********************/
void TX_data(unsigned char Data)
{
TXREG=Data;
while(TRMT==0);

}
上述代码正常工作时,用串口工具任意发送一个字节,即会返回一个16进制的字节。这里一定要用串口的HEX显示。返回的字节应该是“A”,也就是十进制的10。若失败没有返回值,则说明没有写入进24C02,检查电路。
在代码中,我们用到的是IIC的主机模式,IIC_init()相应的配置位的意义看手册看144页。其中注意一点,使用IIC功能前必须打开SSPEN使能IO特殊功能,并且IO方向寄存器要设置成输入模式。
这里见手册144页

其他的没啥可说的。
下面着重介绍PIC的IIC主机写模式是如何工作的。其实最好的办法就是看IIC的时序图168页再结合代码。

IIC_write(uchar Data);函数就是根据上面的时序开始主机写操作的。SDA与SCL时序波形可以简单的看一下,这是单片机自动处理的。
SSPIF在手册中断里面70页。这个位是主同步串口中断标志位。
SSPBUF是IIC的数据缓冲区,不管是写还是读均通过这个寄存器来完成。
下面我将代码写出,分别用数字对准上面的时序图。
方便阅读,我重新截图:

SSPIF=0;//1开始前将标志位清0
SEN=1;//启动位2 准备开始IIC
while(!SSPIF);3当SSPIF置1此刻表示开始发送指令
SSPIF=0;4软件清0
SSPBUF=0xA0;//写操作指令,这里注意,其实在3处
while(!SSPIF);5写数据延迟,直到6
SSPIF=0;7再次清0
SSPBUF=0x01;//写地址指令
while(!SSPIF);8发送数据等待直到9

SSPIF=0;
SSPBUF=Data;
while(!SSPIF);//重复7-9的步骤 这里表示连续发送
SSPIF=0; 10标志位清0
PEN=1;//11停止IIC
while(!SSPIF);12延迟等待
SSPIF=0;13标志位清0
delay(1);//软件延迟
上述指令关键的就是:SEN,SSPIF,SSPBUF,PEN这四位状态。
一套完整的IIC发送指令就是1-13这个流程步骤。(多出来的代码中蓝色部分代码是为了配和24C02的IIC通讯结构。下面会简单的介绍一下24C02的IIC通讯结构。)
IIC通讯我们一般的会有开始指令和结束是指令,这里就是用SEN和PEN表示。记住就可以了,不用深究。while(!SSPIF);这种操作可以理解为等待从机接收应答的确认位,这是IIC通讯都具有的。所以发送指令,其实就3段:
SSPIF=0;
SSPBUF=Data;
while(!SSPIF);
结合AT24C02的手册中字节写总线活动:

注意一下,要想完成对24C02一次写的操作,就必须发送三个字节的数据(控制字节,地址字节,数据字节)。上图中起始位就是PIC的开始SEN,确认位的意思是说,当24C02接收有效字节后,会给主机一个信号,而程序中的while(!SSPIF)就是等待这个确认位。
PIC的读指令和写指令大概的原理上差不多,多了RSEN,RCEN,ACKEN,这三位。其实这也很好理解。RSEN和SEN的功能类似。RCEN表示此时的SSPBUF处于接收模式。而ACKEN表示接收完毕后一个确认位,告诉传数据给我的,我已经接收完毕,请继续发数据给我的意思。上述程序,我针对的是读24C02存储器,对应地址里面寄存器的数值。按照24C02的手册:

按照手册中的描述,在IIC通讯读24C02某个地址中的数值,首先你必须定位24C02的地址。所以在读的里面,我们增加了一段定位地址的代码。
定位地址可以用写地址的方式,这个对没有用过IIC的同学,需要自己理解。
SSPIF=0;这里和前面介绍的写指令是没有区别的。
SEN=1;//启动位
while(!SSPIF);
SSPIF=0;
SSPBUF=0xA0;//写指令,这个指令数值参考24C02手册
while(!SSPIF);
SSPIF=0;
SSPBUF=Addr;//写地址,这里就是我们需要定位的地址
while(!SSPIF);

定位好后就是开始读指令:

SSPIF=0;
RSEN=1; //1重新开始
while(!SSPIF);
SSPIF=0;
SSPBUF=(0xA0|0x01);2//读指令 这里发送地址+1表示开始读
while(!SSPIF);3表示发送地址成功,并且从机给出确认
SSPIF=0;
RCEN=1;//4使能接收,准备好接收
while(!SSPIF);5正在接收,当SSPIF置1
Data=SSPBUF;
while(!SSPIF);6表示接收成功
SSPIF=0;7这里需要清零,系统会发送一个确认位给24C02
ACKEN=1;//使能应答位,8表示已发送确认位
while(!SSPIF);9,等待是否连续发送
SSPIF=0;10,这里表示准备结束阶段
PEN=1;//停止位,11停止IIC
while(!SSPIF);
SSPIF=0;
return Data;返回得到的数据
手册169页主机读时序图中反应的是读2个字节的数据,而在24C02中只有一个字节需要读出来,所以代码中就无需写9后面一段时序。直接10处准备结束。
若读字节部分不存在先定位地址,那么IIC_read(uchar Addr)函数中的写部分可以删除。如果读不只一个字节,那么可以将下方的读代码重复多次即可达到连续读的效果。
SSPIF=0;
RCEN=1;//使能接收
while(!SSPIF);
Get_Data=SSPBUF;
while(!SSPIF);
SSPIF=0;
ACKEN=1;//使能应答位
while(!SSPIF);

以上代码编写均参考“PIC18F1XK22中文手册” 而写,编程器采用PICC18V9.66.上述文字中的内容均是我在学习PIC中所感,借此加深和巩固。难免存在遗漏或者错误,希望能给大家带去一点帮助。
By:Fred
2018-3-22

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值