《单片机原理及其应用-课程设计》信号发生器的设计


温馨提示:请勿直接抄袭代码,代码仅供学习交流,核心功能语句不公开,且进行脱敏和插入错误语句,防止抄袭,如果你是一名本科生,需要完成对应课程设计,这里建议你好好学习C语言编写逻辑,否则文字内容和结构将晦涩难懂,想直接抄袭的话,是不可能的!!!
别抄袭!别抄袭!别抄袭!

项目介绍

要求:

  • D/A转换固定频率波形输出:按键1按下输出40Hz三角波(或锯齿波、梯形波);按键2按下输出60Hz正弦波;
  • 在LCD上实时显示当前波形的名称、频率及电压幅值;
  • 在仿真示波器及实际示波器中调试出正确的波形;
  • 可调频率:读取可调电位器的电压值,经A/D转换后可用于调节波形频率。频率范围10-100Hz;
  • 上位机控制:可以通过串口调试助手进行参数设置和显示;

项目环境介绍

单片机类型: PIC16F887
开发环境: MPLAB X IDE v5.00 + Proteus 8 Professional
开发语言及编译器: C语言 +xc8 v2.4
硬件条件: *PCF8591、按键、LCD

项目结构介绍

项目工程结构图
工程目录

项目中,主要文件包为softwarehardware,其中:

  • software
    • wave.h
  • hardware
    • adc.c
    • adc.h
    • iic.c
    • iic.h
    • lcd.c
    • lcd.h
    • usart.c
    • usart.h

项目结构图

结构图

项目内容

IIC技术

IIC是一种通讯协议,其本质属于串行通讯。其利用SCL,SDL,GND三根线实现设备之间的通讯,在STM32中可以通过标准库函数很轻易调用IIC通讯;通常来说,单片机中的IIC和SPI协议支持的子模块都是同一个硬件模块。在PIC单片机中,其IIC通讯需要从底层的寄存器出发设置参数,书写更为不便,因此,基于其特性,可以利用C语言将其寄存器调用封装为标准库函数,便于后续调用,后续的类似功能例如ADC也采用这类的方法书写。参照STM32中的书写,C语言需要构建.c和.h文件,且需要通过include进行关联。
IIC代码如下
下面是.c文件

#include "iic.h"

void IIC_Initial(void){
    TRISC3=1;TRISC4=1;        //RC3 RC4设置为输入
    SSPEN=1;
    SSPCON=0B00111000;
    SMP=0;
    SSPADD=1;
}
//下面IIC通讯中很容易进入死循环
void IIC_Send_Data(unsigned int data){
    SEN=1;                    //产生起始位
    while(SEN==1);            //检测起始位完成
    SSPBUF=0B10010000;        //发送有效地址激活器件,PCF8591激活字节
    while(R_nW==1);           //检测数据是否发送完毕
    while(SSPIF==0){
        wait++;
        if(wait>250){ wait=0;break;}
    }; 
    wait=0;
    while(ACKSTAT==1){
            wait++;
    if(wait>250){ wait=0;break;}
    };        //检测是否应答
    SSPBUF=0x40;              //发送0b01000000,模拟量输出使能
    while(R_nW==1);
    while(SSPIF==0){
        wait++;
        if(wait>250){ wait=0;break;}
    }; 
    wait=0;
    while(ACKSTAT==1){
        wait++;
        if(wait>250){ wait=0;break;}
    };        //检测是否应答
    wait=0;
    SSPBUF=data;                 //发送需要发送的数据
    while(R_nW==1);
    while(SSPIF==0){
        wait++;
        if(wait>250){ wait=0;break;}
    }; 
    wait=0;
    while(ACKSTAT==1){
        wait++;
        if(wait>250){ wait=0;break;}
    };        //检测是否应答
    PEN=1;                    //停止位
}

下面是.h文件

#ifndef __IIC_H
#define	__IIC_H
#define SCL RC3
#define SDA RC4
#include <xc.h> 
//设备的地址定义如下,可以根据设备侧的实际地址设置而改变
#define PCF_ADDR 0X90
char wait=0;
void IIC_Initial(void);
void IIC_Send_Data(unsigned int data);
#endif

ADC技术

ADC技术很简单,就是A->D的一种技术,PIC16F887中的ADC属于逐次逼近型ADC,且位数为10位。对于一般性应用来说,够用了。逐次逼近型ADC本质就是一堆数字逻辑器件加上DAC模块和寄存器,在单片机中,其是作为单片机的一个硬件模块。通过配置寄存器可以实现ADC功能。

#include "adc.h"
void AD_Initial(void)
{
    ANSELH=0;//模拟选择
    OPTION_REG=0B10000000;//禁止弱上拉,下降沿触发中断
    TRISE0=1;//模拟口输入,用于ADC采样
    TRISB=0B11111100;//将检测外部电平变化全部初始化,并且
    IOCB=0B11111100;//电平中断变化使能
    TRISD=0B00000000;//LCD部分的所有端口都配置为输出,控制LCD
    PR2=48;//初始波形周期设置
    RBIE=1;
    PEIE=0;
    TMR2IE=1;
}
unsigned int  AD_SUB(unsigned char k)
{
    unsigned char i;
    ADCON1=0B10000000;
    ADCON0=0B01000001;
    ADCON0=ADCON0|(k<<2);
    for(i=0;i<3;i++) NOP();
    GO=1;
    while(GO==1);
    return((ADRESH<<8)+ADRESL);
}

//BCD转换函数
void BCD(unsigned int R1)
{
    QW=0;BW=0;SW=0;GW=0;
    while(R1>=1000)
    {
        R1-=1000;QW++;
    }
    while(R1>=100)
    {
        R1-=100;BW++;
    }
    while(R1>=10)
    {
        R1-=10;SW++;
    }
    GW=R1;
}

void __FUZHI(void)
{
    if(F==0)
    {
        GG[2]='5';GG[4]='0';GG[5]='0';
    }
    if(F==1)
    {
        GG[2]='2';GG[4]='5';GG[5]='0';
    }
    if(F==2)
    {
        GG[2]='1';GG[4]='2';GG[5]='5';
    }
    if(F==3)
    {
        GG[2]='0';GG[4]='6';GG[5]='3';
    }
}

void __ZHOUQI(void)
{
    PR2=(AD_SUB(5)>>2);
    if(PR2>240) PR2=240;
    if(PR2<4)  PR2=4;
    T=(PR2+1)*(-0.58);
    T=(T+54)*10;
//    f=(1.0/T);
    if(T<=100){
        GG[11]='0';GG[12]='1';GG[13]='0';
    }
    if(T>=500){
        GG[11]='0';GG[12]='7';GG[13]='4';
    }
    else{
    BCD(T);
    GG[11]=QW+'0';
    GG[12]=BW+'0';
    GG[13]=SW+'0';
    }
}

下面是.h文件,上面是.c文件

#ifndef __ADC_H
#define	__ADC_H

#define _XTAL_FREQ 4000000

#include <xc.h> 
#include "lcd.h"
extern unsigned int QW,BW,GW,SW;
unsigned int QW,BW,GW,SW;
unsigned int A=0,M=0;
unsigned int F=0;
unsigned int T;
double f;
void AD_Initial(void);
unsigned int  AD_SUB(unsigned char k);
void BCD(unsigned int R1);
void __FUZHI(void);
void __ZHOUQI(void);

#endif

LCD显示技术

LCD主要是通过位操作,其通过配置单片机I/O口和控制高低电平来实现与LCD通讯,从而实现对LCD的控制。

#include "lcd.h"
#define _XTAL_FREQ 4000000
// 说明时钟频率 在下面调用 __delay_us(char a);__delay_ms(char a); (注意这里delay前面是两条下划线) 
//初始化语句
void LCD_Init(void){
//    INTCON=0;//先关闭中断使能
    TRISD=0X00;//设置所有PORT所有引脚为输出
    DELAY_MS(20);
	LCD_WRITE_4(0b0011,COM);//发送三次控制序列
	DELAY_MS(1);
	LCD_WRITE_4(0b0011,COM);
	DELAY_US(10);
	LCD_WRITE_4(0b0011,COM);
	DELAY_US(10);
	LCD_WRITE_4(0b0010,COM);//命令6,4位数据格式
	LCD_BUSY();
	LCD_WRITE(0b00101000,COM);//命令6,功能设置,设置行数N,点阵F
	DELAY_US(10);
	LCD_WRITE(0b00001100,COM);//命令4,显示控制,打开显示,不显示光标,关闭闪烁
	DELAY_US(10);
	LCD_WRITE(0b00000001,COM);//命令1,清屏
	DELAY_MS(2);
	LCD_WRITE(0b00000110,COM);//命令3,输入模式设置,光标右移,禁止整体移动
    DELAY_MS(2);
}
//写入半个字节
void LCD_WRITE_4(char R1,char FLAG){
    //写入四位字节
    LCD_RW=0;//写模式
    DELAY_US(2);
    LCD_RS=FLAG;//选择输入命令还是数据
    DELAY_US(2);
    LCD_E=1;//抬高使能引脚
    DELAY_US(2);
	PORTD|=R1;
    DELAY_US(2);
	LCD_E=0;//下降沿写入数据
    DELAY_US(2);
	LCD_RS=0;//调整为默认状态命令
    DELAY_US(2);
	PORTD&=0XF0;//复位
}
//===============================================
//读取数据
char LCD_READ(void)
{	unsigned char R1;
	LCD_RS=0;		//寄存器选择,0为命令
    DELAY_US(2);
	LCD_RW=1;		//读为1
    DELAY_US(2);
	LCD_E=1;		//使能,下降沿有效
    DELAY_US(2);
	R1=0;			//短延时
	R1=(PORTD<<4)&0xF0;	//读PORTD的高4位给R1
	LCD_E=0;
    DELAY_US(2);
	LCD_E=1;		//使能,下降沿有效
    DELAY_US(2);
	R1|=(PORTD&0x0F);	//读PORTD的低4位,R1的高4位不变
	LCD_E=0;
    DELAY_US(2);
	LCD_RW=0;
    DELAY_US(2);
	return(R1);
}
//===============================================
//忙检测
void LCD_BUSY(void)
{	unsigned char R1;
	while(1)
	{	R1=LCD_READ();	    	//读寄存器
        DELAY_US(5);
		if((R1&0x80)==0x00)	//最高位为忙标志
			break;
	};
}
//===============================================
//发送一整个字节,带忙检测
void LCD_WRITE(char R1,char FLAG)
{	char R2;
	LCD_BUSY();//忙检测,LCD反应速度没有单片机那么快,忙检测保证LCD可以正常接收数据;
	R2=R1&0xF0;//先赋值高八位
	R2=R2>>4;//把高八位右移后作为四个bit组成半个字节发送出去
	LCD_WRITE_4(R2,FLAG);//发送
	R2=R1&0x0F;//赋值第四位
	LCD_WRITE_4(R2,FLAG);//发送
	DELAY_US(10);//必要延迟
} 
void LCD_WriteStr(char line,char *s ){
    char i;
    LCD_WRITE(line,COM);
    for(i=0;i<16;i++)
		LCD_WRITE(s[i],DATA);	//写16个数据,即显示1行
}

//===============================================
//延时(n)ms
void DELAY_MS(unsigned int n)
{	unsigned int j;
	char k;
	for(j=0;j<n;j++)
		for(k=108;k>0;k--)
		NOP();
}
//===============================================
//延时(n*10)us+12us,精确,包括调用与返回时间
void DELAY_US(char n)
{	
    char j;
	j=n;
	while(j>0)
	{	j--;
		NOP();NOP();NOP();NOP();
	}
}

下面是.h文件,上面是.c文件

#ifndef __LCD_H
#define	__LCD_H

#include <xc.h> 

#define LCD_E RD6 //使能,下降沿有效
#define LCD_RW RD5 //读写功能引脚;1:读;0:写
#define LCD_RS RD4 //命令和数据选择端 1:数据;0:命令
#define COM 0
#define DATA 1
#define LINE1 0b10000000
#define LINE2 0b11000000
extern char DD[16],CC[16],MM[16],GG[16],CK[16];
char DD[16]={"**SG Generator**"},
     CC[16]={"  Author: Gritty "},
        MM[16]={"wave            "},
        GG[16]={"A=5.00V**F=100HZ"},
        CK[16]={"A=5.00V**F=100HZ"};
void LCD_Init(void);
void LCD_WRITE_4(char R1,char FLAG);
char LCD_READ(void);
void LCD_BUSY(void);
void LCD_WRITE(char R1,char FLAG);
void DISP_C(char);
void LCD_WriteStr(char line,char *s );
void DELAY_MS(unsigned int n);
void DELAY_US(char n);
void Disp_fre_Am(unsigned int frequence,unsigned int voltage,unsigned char sg);
#endif

串口通讯技术

串口通讯协议较为简单,其由RX,TX和GND实现通讯,其可以通过TTL转USB实现与上位机通讯,也可以通过一些其他的转换协议,实现与上位机通讯,其是最基本的通讯技术。

#include "usart.h"
#define _XTAL_FREQ 4000000
void USART_Initial(void){
    TRISC=0B10111111; //定义端口输出输入情况
    RCIE=1;//串行通信接收中断使能
    SPBRG=25;
    TXSTA=0B01100100;
    RCSTA=0B11010000;
}
void USART_Send(char a){
    TXREG=a;
    while(TRMT==0);
}

下面是.h文件,上面是.c文件

#ifndef __USART_H
#define	__USART_H
#include <xc.h>
//#define TX1 RC6
//#define RX1 RC7

void USART_Initial(void);
void USART_Send(char a);
#endif

波形采样技术

通过MATLAB可以生成采样的波形,得到采样的点数,MATLAB代码较为简单,可以自己书写。
生成的点数文件如下

#ifndef __WAVE_H
#define	__WAVE_H
const char sin[44]={
128,146,164,181,197,212,225,236,244,251,255,255,255,251,244,236,225,212,197,181,164,146,128,110,92,75,59,44,31,20,12,5,1,0,1,5,12,20,31,44,59,75,92,110

};
const char saw[64]={
    0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,
    164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252
};
const char sin_high[28]={
128,156,184,208,228,243,253,255,253,243,228,208,184,156,128,100,72,48,28,13,3,0,3,13,28,48,72,100

};
const char saw_high[32]={
    0,8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128,136,144,152,160,168,176,184,192,200,208,216,224,232,240,248

};
#endif	/* XC_HEADER_TEMPLATE_H */

核心技术

  • 核心技术为有限状态机
    FSM,利用FSM实现单片机在不同状态的切换。在不同的状态下,单片机处于不同的状态;利用判断条件,实现单片机在不同的状态下的跃迁;
  • 在输出调节中采用数值逼近技术,利用线性逼近实现定时器时间与输出频率之间的准确响应;
  • 总纲: 程序经过初始化后就进入 while 循环或中断两种工作状态。在 while 循环状态中,主要是处理复杂的需要较大运算量的工作。这就包括显示信息、频率控制、控制输出和旋钮调频操作。这些操作都需要一定的运算量,具有一定的时间复杂度。在中断工作下,主要完成 I/O 口电平检测、波形输出和串口数据接收。这些操作需要的时间很短,I/O 口电平检测和串口数据接收后会关闭中断功能,退出中断,回到主函数中,完成更新状态的操作。程序功能主体由波形发生、频率控制、显示信息、控制处理和旋钮调频组成;
  • 波形发生: 通过定时器 2 中断,每隔一段时间通过 IIC 通讯向 PCF8591 发送波形数组中的一个数据,PCF8591 将数据处理后输出电平信号。只要保证输出数组的大小合适且发送时间间隔不太大,在 PCF8591 输出就可以得到接近需要的波形信号,后级电路经过滤波就可以得到更加符合要求的波形信号;
  • 频率控制: 频率控制主要用到的定时器 2 的定时功能。定时器 2 初始化后,通过改变定时器2的PR2寄存器,改变发送数据之间的时间间隔,实现改变频率。显示信息:通过四线制工作方式控制 LCD1602,在频率、赋值和波形类型发送变化的时候更新液晶显示屏上的信息;
  • 控制处理: 1.按钮控制:控制处理中用到了三个按钮,分别对应功能是模式切换、频率刷新、串口刷新。按钮触发通过 RB 口中断实现,利用边沿触发模式,实现对 RB 口电平变化的检测。模式切换利用对 RB0 口触发次数的计数。模式共有 4 种,分别是固定频率输出正弦、固定频率输出锯齿波、变化频率输出正弦波和变化频率输出锯齿波。通过对触发次数计数,每次触发次数储存在 count_wave(模式状态的标准位)中;
  • 频率刷新是利用 RB1 口电平中断,一旦触发中断就就改变全局变量 count_frequence(刷新频率标志位)的值;串口刷新也是利用 RB2口电平中断,一旦触发该中断就改变 count_usart(刷新串口标志位);
  • 串口控制: 当串口中断触发后,将数据接收后存在全局变量的数组中;当发生按钮控制的信号触发后,就将中断总开关关闭,使其退出中断,回到主函数当中。在主函数的 while 循环中,根据标志位进行相对应操作;
  • 旋钮调频: 当旋钮变化后,按下控制处理 RB2 按钮,count_frequence 标志位置 1,退出中断。在主函数 while 循环中,count_frequence=1 进入频率更新模式,AD 采样一次后,通过频率与 ADC 采样结果对应函数关系改变 PR2 值,实现对输出波形频率的改变。
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值