发新帖我要提问
12
返回列表
打印
[学习资料]

PIC16F887 实战编程 单片机编程 基础实验教程

[复制链接]
楼主: gwsan
手机看帖
扫描二维码
随时随地手机跟帖
21
gwsan|  楼主 | 2021-7-6 09:15 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
题外话:编译的时候提示了一句话:

You have compiled in FREE mode.


题外话:所以如何才能不处于免费模式?右键工程–>properties–>按图里的点击。
题外话:作用:此选项选择编译器的基本操作模式。可用类型有pro、std和free。在PRO模式下运行的编译器使用完全优化并生成最小的代码大小。标准模式使用有限的优化,而自由模式只使用最小的优化级别,将生成相对较大的代码。
提示:选择PRO在很多时候都不是一个明智的选择,PRO模式在编译器会“智能地”改变一些C语言的汇编实现方法,这有时候会有益于代码的最优化,但有时候会显得有些“智障”,会让能你认为能好好工作的C代码工作起来不正常。这个PRO主要是提供给有经验的嵌入式开发工程师用于优化代码的。


使用特权

评论回复
22
gwsan|  楼主 | 2021-7-6 09:16 | 只看该作者
7、
MPLAB中的事情已经进行完了,现在你已经知道怎么使用MPLAB建立工程,如何编写程序和编译程序,编译后的hex文件目录在哪里。
其实写好程序后,就可以链接仿真器进行下载(烧写)程序了。链接仿真器后,下面的run图标或者debug图标点一下,MPLAB都会自动讲hex文件烧写到实物单片机中,单片机就能够运行起来。这一步的操作是在调试实物的时候所用到的,字眼是debug。这里只是顺便提及一下,跟我们想要进行的仿真没有丝毫联系。


现在可以谈谈另一件事情,如何proteus仿真?
Proteus软件里面含有很多单片机和外部器件,能够模拟实物电路的运行。如果我们在Proteus软件里连接好需要的实物器件,然后将写好的程序(hex文件)加载到Proteus软件里的单片机器件里,那在Proteus软件中我们就可以看到整个电路的工作情况。我们的关注点就放到了Proteus软件中的器件连接中了。


使用特权

评论回复
23
gwsan|  楼主 | 2021-7-6 09:17 | 只看该作者
8、

新建仿真图–>添加器件PIC16F887


使用特权

评论回复
24
gwsan|  楼主 | 2021-7-6 09:18 | 只看该作者
将hex文件加载到Proteus软件里的单片机器件里:双击单片机器件进入下图这个设置界面—>点击打开文件符号—>选择桌面上工程里的hex文件—>打开—>点击OK—>成功。这里没有详细截图,看下图里的左边箭头去找hex文件即可!


使用特权

评论回复
25
gwsan|  楼主 | 2021-7-6 09:20 | 只看该作者
此时可以看到Proteus软件左下角,分别是开始仿真和停止仿真按钮。我们点击开始仿真。


使用特权

评论回复
26
gwsan|  楼主 | 2021-7-6 09:21 | 只看该作者
仿真运行后,可以看到单片机的端口B四位电平,蓝色就是低电平的意思,红色就是高电平的意思,这里就是0101的电平。如果这引脚连接了LED灯,LED灯就可以点亮。由此可见,程序生效了,单片机正常工作中。

使用特权

评论回复
27
gwsan|  楼主 | 2021-7-6 09:22 | 只看该作者
3 单片机基础寄存器操作:
3.1 IO
设置一个端口为输出 TRISA0=0
设置一个端口为输入 TRISA0=1
设置一个端口输出高电平 RA0=1
设置一个端口输出低电平 RA0=0
同时设置8个端口响应使用TRISA PORTA
设置B C D端口类似。

3.2 模拟输入电压读取
设置某个引脚为输入–>打开模拟输入

3.3 外部中断
详细看书。

3.4 定时器中断
详细看书。

3.5 串口UART
详细看书。

3.6 IIC通信
详细看书。


使用特权

评论回复
28
gwsan|  楼主 | 2021-7-6 09:24 | 只看该作者
4 实际项目
PIC16F887 单片机 PROTEUS 仿真 C程序 测温系统 TC74 DS18B20
PIC16F887 单片机 PROTEUS 仿真 C程序 信号发生器
PIC16F887 单片机 PROTEUS 仿真 C程序 温控电机
PIC16F887 单片机 PROTEUS 仿真 C程序 数字时钟 万年历 DS1302 阴历显示
PIC16F887 单片机 PROTEUS 仿真 C程序 抢答器
PIC16F887 单片机 PROTEUS 仿真 C程序 可存储电子琴 PIC
PIC16F887 单片机 PROTEUS 仿真 C程序 模拟电话拨号计算器 密码锁
PIC16F887 单片机 PROTEUS 仿真 C程序 电子密码锁
PIC16F887 单片机 PROTEUS 仿真 C程序 智慧门铃呼叫系统 门铃一拖 3
PIC16F887 单片机 PROTEUS 仿真 C程序 病床呼叫系统
PIC16F887 单片机 PROTEUS 仿真 C程序 测温系统 DS18B20 TC74
PIC16F887 单片机 PROTEUS 仿真 C程序 数字点餐系统


使用特权

评论回复
29
gwsan|  楼主 | 2021-7-6 09:30 | 只看该作者
5 如何阅读代码

整个程序结构一般如下:

#include <xc.h>
//part 0//
可能会放置一些子函数的声明
C语言嘛,子函数无非就是先声明,后定义。


//part 1//
各种杂七杂八的子函数放在这个地方
显示屏子函数
温度传感器子函数
报警判断子函数
按键检测子函数

void main( void )
{
    //part 2//
        调用一些初始化程序
        比如单片机引脚要读取按键高低电平,就得把这个引脚设置为输入方向
        比如定时器中断初始化,设置每隔2ms执行一次中断函数
       
        while(1)
        {
                //part 3//
                这里是一个死循环体
                这个位置要写单片机不断重复在做的事情,永不停息
                比如检测按键输入,有输入了根据输入处理一下
                比如不断调用温度读取函数,然后把结果显示出来
               
        }
       
       
}

//part 3//
这里这个带了interrupt 这个函数就是中断函数
这个函数独立于所有函数之外,单独看这个函数
这个函数是需要被触发的
都是要靠设置单片机寄存器来做到的
比如在main里初始化的时候设置了定时器1的2ms中断,那么每过2ms就会执行一次high_isr()函数,在函数里面判断寄存器TMR1IF 就可以知道是定时器的2ms事件。
void interrupt high_isr( void )
{
        if ( TMR1IF )
        {
                TMR1IF        = 0;//清除标志位
                //part 4//
                这里需要写定时执行的东西,
                比如我设置了一个变量是秒针倒计时,每一秒就减少1
                那么我得这么写:
                变量1 ++;
                if(变量1加到了500)
                {
                        变量1 = 0;
                        秒针 --;  (变量1每2ms执行一次减少1,500次花费时间1s)
                }

    }
}


使用特权

评论回复
30
gwsan|  楼主 | 2021-7-6 09:33 | 只看该作者
看懂上面的框架后,我们就可以阅读更多的代码,我们可以依照以下几个注意点去看程序:
(1)在MPLAB中建立好工程,利用好MPLAB去阅读程序;
(2)MPLAB有个窍门,在工程已经被编译后,我们可以 按住ctrl 然后鼠标左键点函数名字或变量名字去跳转,直接跳转到函数定义的地方。更需要提出的是,在子函数定义处再次ctrl+左键点函数名字,会跳转到子函数声明处。
(3)ctrl+F进入查找,可以通过Next去查看main.c整个文件中的这个查找字符串出现的每一个地方。方便看函数都在哪里用了。


(4)注重函数或者变量名称+注重分类。比如LCD1602驱动函数就是挨着的一堆,函数名都差不多有个LCD字样。有意识地去多输入一个回车,使得与其他代码间隔远一下,自己更好分清。

(5)建议别从main.c文件的第一行开始看,懂了整个程序结构这个鸭子类型后,单片机上电后就执行main()函数里的,我们就可以直接从main()开始看,先初始化啥啥啥的,然后死循环啥的。别的子函数都是调用,既然在递归,那我们更需要关注最上层,从最上面的调用通过注意点(2)的窍门去看。这样更能注意到 题目功能的实现走向,而不是注意到底层都怎么写的,为什么那么写。

注意:我这里再梳理一下,如何看懂别人的程序是有一个过程的。首先你在脑海里面要有一个程序的大体结构,然后你应该从main()函数开始看,利用鼠标哪里不会点哪里,看看注释帮助自己理解这个函数是在干啥, C语言库函数不理解的直接就baidu学习。其实了解程序结构以后就大概了解整个实现功能的过程,我们无非就是去关注上电之后的初始化过程、主函数在循环处理的过程、中断函数在中断处理哪些事件的过程。最终最终代码就是要实现功能,我们能把代码和功能相关联起来那就成功了!


使用特权

评论回复
31
gwsan|  楼主 | 2021-7-6 09:40 | 只看该作者
6 如何把代码放到MPLAB V5.0+xc8 v2.0上工作?

6.1 短暂的回顾

这个话题其实我不太想说,因为以前很多代码资料都是建立在PICC或者xc8 v1.41上(包括大家的教科书上),代码资料多了,做设计就相对来说比较容易,因为这就意味着你可以去找别人的代码资料复制到自己的工程里面去用,并且不用担心因为编译器不同带来的影响(编译器差距太大,会导致最后编译出来的代码不一样,可能我之前在淘宝找的资料能够用xc8 v1.41编译器在实物上运行,修改编译器可能会导致很大的改变,这是我不愿意看到的,不愿意花时间去调试这些bug)。
但总有人问,我来梳理一下怎么去移植。
首先让我们去关注一下xc8 v1.41上面的程序:

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* CONFIG1 */
#pragma config FOSC = XT        /* Oscillator Selection bits (XT oscillator: Crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN) */
#pragma config WDTE = OFF       /* Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register) */
#pragma config PWRTE = OFF      /* Power-up Timer Enable bit (PWRT disabled) */
#pragma config MCLRE =ON        /* RE3/MCLR pin function select bit (RE3/MCLR pin function is digital input, MCLR internally tied to VDD) */
#pragma config CP = OFF         /* Code Protection bit (Program memory code protection is disabled) */
#pragma config CPD = OFF        /* Data Code Protection bit (Data memory code protection is disabled) */
#pragma config BOREN = OFF      /* Brown Out Reset Selection bits (BOR disabled) */
#pragma config IESO = OFF       /* Internal External Switchover bit (Internal/External Switchover mode is disabled) */
#pragma config FCMEN = OFF      /* Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled) */
#pragma config LVP = OFF        /* Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming) */

/* CONFIG2 */
#pragma config BOR4V = BOR40V   /* Brown-out Reset Selection bit (Brown-out Reset set to 4.0V) */
#pragma config WRT = OFF        /* Flash Program Memory Self Write Enable bits (Write protection off) */


void main( void )
{

        while(1)
        {
       
        }
       
       
}
void interrupt high_isr( void )
{
       
}


使用特权

评论回复
32
gwsan|  楼主 | 2021-7-6 09:53 | 只看该作者
上面的程序就是一般的xc8 v1.41上面的程序,一起来看一下都有哪些部分。

第1个部分是头文件,其实这个部分更应该放在接下来所要说的第2个部分之后。

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>



第2个部分就是编译器预设置,这个部分xc8 v1.41和xc8 v2.0一样规则。#pragma这种指令就是去设置编译器的,告诉编译器应该怎样编译这个c程序。

/* CONFIG1 */
#pragma config FOSC = XT        /* Oscillator Selection bits (XT oscillator: Crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN) */
#pragma config WDTE = OFF       /* Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register) */
#pragma config PWRTE = OFF      /* Power-up Timer Enable bit (PWRT disabled) */
#pragma config MCLRE =ON        /* RE3/MCLR pin function select bit (RE3/MCLR pin function is digital input, MCLR internally tied to VDD) */
#pragma config CP = OFF         /* Code Protection bit (Program memory code protection is disabled) */
#pragma config CPD = OFF        /* Data Code Protection bit (Data memory code protection is disabled) */
#pragma config BOREN = OFF      /* Brown Out Reset Selection bits (BOR disabled) */
#pragma config IESO = OFF       /* Internal External Switchover bit (Internal/External Switchover mode is disabled) */
#pragma config FCMEN = OFF      /* Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled) */
#pragma config LVP = OFF        /* Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming) */

/* CONFIG2 */
#pragma config BOR4V = BOR40V   /* Brown-out Reset Selection bit (Brown-out Reset set to 4.0V) */
#pragma config WRT = OFF        /* Flash Program Memory Self Write Enable bits (Write protection off) */





第3个部分就是主函数,这个部分xc8 v1.41和xc8 v2.0一样规则。

void main( void )
{

        while(1)
        {
       
        }
       
       
}



第4个部分中断,这个部分xc8 v1.41和xc8 v2.0命名规则不一样。

void interrupt high_isr( void )
{
       
}


使用特权

评论回复
33
gwsan|  楼主 | 2021-7-6 09:54 | 只看该作者
6.2 xc8 v2.0程序结构
下面给出一个xc8 v2.0上面的程序,记住程序结构:


/* CONFIG1 */
#pragma config FOSC = XT        /* Oscillator Selection bits (XT oscillator: Crystal/resonator on RA6/OSC2/CLKOUT and RA7/OSC1/CLKIN) */
#pragma config WDTE = OFF       /* Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register) */
#pragma config PWRTE = OFF      /* Power-up Timer Enable bit (PWRT disabled) */
#pragma config MCLRE =ON        /* RE3/MCLR pin function select bit (RE3/MCLR pin function is digital input, MCLR internally tied to VDD) */
#pragma config CP = OFF         /* Code Protection bit (Program memory code protection is disabled) */
#pragma config CPD = OFF        /* Data Code Protection bit (Data memory code protection is disabled) */
#pragma config BOREN = OFF      /* Brown Out Reset Selection bits (BOR disabled) */
#pragma config IESO = OFF       /* Internal External Switchover bit (Internal/External Switchover mode is disabled) */
#pragma config FCMEN = OFF      /* Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is disabled) */
#pragma config LVP = OFF        /* Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming) */

/* CONFIG2 */
#pragma config BOR4V = BOR40V   /* Brown-out Reset Selection bit (Brown-out Reset set to 4.0V) */
#pragma config WRT = OFF        /* Flash Program Memory Self Write Enable bits (Write protection off) */

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main( void )
{

        while(1)
        {
       
        }
       
       
}
void __interrupt() ISR( void )
{
       
}



使用特权

评论回复
34
gwsan|  楼主 | 2021-7-6 09:56 | 只看该作者
6.3 移植操作指南

1、假如你的程序中含有中断函数,你应该把xc8 v1.41的中断名字改为xc8 v2.0的中断名字。(题外话:做了这一步你的程序基本就OK了,但我也没有调试过实物,不知程序到实物是否正常,建议能不搞xc8 v2.0就别搞,一时的安逸可能带来更多的挫折。)
!!!!据说应该在main()函数之前添加中断的函数声明:void __interrupt() ISR( void ); 不然会乱码。(侧面反应出我们并不熟悉xc8 v2.0,这里行了也不能保证实物正常吧。)
xc8 v1.41的中断名字

void interrupt high_isr( void )


xc8 v2.0的中断名字

void __interrupt() ISR( void )



2、如果你的程序中含有DS1302驱动程序,并且程序中还有下面两句话:

unsigned char                time_rx @ 0x30;                         /* 定义接收寄存器 */
static volatile bit        time_rx7 @ (unsigned) &time_rx * 8 + 7; /* 接收寄存器的最高位 */




这种写法在xc8 v2.0中不再支持,这两句话其实是去取最高位地址bit数据,写法比较高级。你需要把这两句话删除掉。并且你需要把下面的函数修改为下面这个样子(可以直接复制下面这个函数去替换程序中原来的函数,找好位置 别搞错了)。

/****************************************************************************
* 名    称:time_read_1()
* 功    能:读一个字节
* 入口参数:
* 出口参数:
* 说    明:
****************************************************************************/
unsigned char time_read_1()
{
        int j;                                  /* 设置循环变量 */
    unsigned char  time_rx;
        TRISC4 = 1;                             /* 设置数据口方向为输入 */
        for ( j = 0; j < 8; j++ )               /* 连续读取8bit */
        {
                sclk                = 0;            /* 拉低时钟信号 */
                time_rx                = time_rx >> 1; /* 接收寄存器右移1位 */
        if(i_o)
        {
            time_rx |= 0x80;
        }
                sclk                = 1;            /* 拉高时钟信号 */
        }
        TRISC4        = 0;                            /* 恢复数据口方向为输出 */
        sclk        = 0;                            /* 拉低时钟信号 */
        return(time_rx);                        /* 返回读取到的数据 */
}




3、你或许更应该将编译器预设置部分的代码放到程序的最开始,但我个人认为其实没什么影响。

4、如果你在移植操作中遇到更多的报错信息,可以告诉我。我的处理方式也很简单,我会根据软件下面的报错信息找到对应的报错点,然后根据经验修改报错点程序即可。


使用特权

评论回复
35
gwsan|  楼主 | 2021-7-6 09:57 | 只看该作者


看到这里,你应该有一个基本观念,不要拿“我们没学过”当借口,遇到程序看不懂就知道退缩,怎么学也学不会。
多找资料,多复习C语言,遇到问题敢于写个小程序做做实验。你自己想要写出自己的程序的第一步,就是模仿学习别人的程序。

7.1 关于LCD1602

LCD1602使用LCD_WRITE()函数执行写入命令操作或者写入数据操作。
写入命令0x80+x,代表想要显示的起始位置是LCD1602的第0行,第x列。(一共有2行,16列,x可以等于0到15,y可以等于0到1)。
写入命令0xC0+x,代表想要显示的起始位置是LCD1602的第1行,第x列。
char *s是一个指针,指向一个字符串首地址的话,while (*s)会一直判断当前是不是为空,不为空就向1602写入指针指向的字符 LCD_WRITE(*s, DATA);,写入后指针后移一个字符 s++。
举例 lcd1602_write_str(3,1,“hello”); 就是想在第3列,第1行开始显示,显示一个字符串hello。指针最初指向’h’,写入后后移,直到指针指向空。

/* 写字符串 */
void lcd1602_write_str(unsigned char x, unsigned char y, char *s)
{
    if (y == 0)
    {
        LCD_WRITE(0x80 + x, COM);//写入命令
    }
    else
    {
        LCD_WRITE(0xC0 + x, COM);//写入命令
    }

    while (*s)
    {
        LCD_WRITE(*s, DATA);//写入数据
        s++;
    }
}


使用特权

评论回复
36
gwsan|  楼主 | 2021-7-6 09:59 | 只看该作者
7.2 memset清空 sprintf装填

学了void lcd1602_write_str(unsigned char x, unsigned char y, char *s)函数后,写显示函数就简单了。
你会定义一个数组,比如是 buffer[16]。
当你想显示hello的时候,你可以这么写:

buffer[0]='h';
buffer[1]='e';
buffer[2]='l';
buffer[3]='l';
buffer[4]='o';
buffer[5]=0; //必须给0,指针指到这里认出0就代表空了
lcd1602_write_str(3,1,buffer);//指针指向数组的首位,然后指针一直后移,最终写入完整hello


但上面的方法不方便,比如你想显示一个变量abc,你得这么写:

int abc=54;
buffer[0]='0'+abc%100/10;//百分号是取余运算,/是整除运算,可百度。加'0'是为了变成ascii码。
buffer[1]='0'+abc%10;
buffer[2]=0;
lcd1602_write_str(3,1,buffer);


C语言发明者就发明了一个通用函数sprintf,这让这个操作简单了。
比如你想把字符串“hello”装入数组里,你可以直接:

sprintf(buffer, "hello");//装填


C语言就会把字符串一个一个填入数组,这个时候你就可以调用类似于lcd1602_write_str(3,1,buffer);这样去显示了。
比如你想把abc变量变成字符串然后写入数组,你可以直接:

sprintf(buffer, "hello %d",abc);//装填  %d是十进制表示的意思
sprintf(buffer, "hello %d  %d",abc, abc);//可以一次装填多个变量


第一个参数是数组指针 buffer
第二个参数是字符串样式
第三个参数和后面参数是一些变量
这个时候还有个问题,就用sprintf(buffer, "hello");//装填举例,执行这句话后数组里是变成字符串了,此时有:

buffer[0]='h';
buffer[1]='e';
buffer[2]='l';
buffer[3]='l';
buffer[4]='o';


但是并不能保证数组里面其他的元素是0!!
C语言发明者就搞了一个memset函数,能够初始化一切数组。
此时就可以:

memset(buffer, 0, sizeof(buffer));


第一个参数是数组指针 buffer
第二个参数是想把数组里面所有元素初始化为0
第三个参数是想初始化多长的元素,sizeof(buffer)表示想初始化数组里所有元素。


使用特权

评论回复
37
gwsan|  楼主 | 2021-7-6 10:01 | 只看该作者
参考书籍:




使用特权

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

本版积分规则