实验三 可变增益放大电路研究
!!!说明!!!:为了方便各位同学能做出来相应的实验,本人将实验报告内容发出,思路操作仅供参考,如果全篇照抄,后果自负哦。
一、实验目的
(1)掌握可变增益放大电路的一种实现方法,实现电路的增益交替变化。
(2)掌握单片机内部SPI串行模块的使用方法,编程实现对SPI器件的写入。
(3)熟悉LCD1602液晶显示模块的常用命令及其功能,能够编程驱动液晶显示ASCII码字符。能够创建自定义字符,并可以通过编程实现将自定义字符显示在屏幕上。
二、实验原理
1.MCP41010芯片介绍
(1)芯片引脚功能的介绍
|
芯片封装图 |
|
|
1号引脚,片选信号角,在上电以后,该引脚上施加低电平时才可以对芯片内部数据进行写入。
2号引脚,时钟信号脚,在对芯片进行数据写入时,需要给其提供时钟信号,在本次实验中写入数据时就要在该引脚上产生16个连续的脉冲对2个字节的数据进行读取。
3号引脚,读取数据脚,这个引脚会接收控制器发出的串行信号。
4号引脚,GND,这个引脚用来接地。
5、6、7号引脚,是芯片内部的电位器,6号引脚为抽头。
8号引脚,VDD,这个引脚用来接电源线,根据其数据手册可以提供2.7V到5.5V的电压。
其中对芯片内部的电位器说明如下:

上图中电位器的抽头PW0位置由数据寄存器(256位)中的数据来确定,当写入数据为00H,抽头PW0位置将移至PB0端,当写入数据为FFH,抽头将移至PA0端。
(2)如何将数据写入芯片内部的数据寄存器
上图为向芯片的数据寄存器写入数据的时序逻辑图。当要往芯片内部写入数据的时候,需要将CS置于低电平,此时芯片被选中;此时数据输入在串行时钟的上升沿被接受,在时钟的下降沿数据输出,本实验用到的芯片只有SI引脚;对于串行数据输入端(SI),串行数据通过此引脚进入移位寄存器,在传送数据时需要传送两个字节的数据,第一个字节为命令字节,第二个字节为要写入 的数据,命令字节包括两个命令选择位和两个电位器选择位(见下图),因此在第一个数据字节中,只需要关注C1 C0 P1 P0 这四个位。

本实验中,使用的是写入数据命令和选择电位器0(因为8引脚芯片只有电位器0)。
(3)RAW和RBW电阻与数据寄存器中数据Dn的关系。

上图为电位器内部结构,本实验芯片采用MCP41010其电位器总电阻为10KΩ(后续实验中实测为10.8KΩ),电阻整体被一个256位的寄存器均分成256份,而数据Dn可指定抽头PW0的位置,从而可以确定由两端PWA、PWB分别至抽头位置处的电阻大小。其关系为(单位为千欧):(假设写入数据为Dn)

2.STC12C5A60S2单片机与SPI功能模块相关的寄存器介绍
与单片机的SPI功能有关的寄存器见上图。其中SPCTL是SPI控制寄存器;SPSTAT是SPI状态寄存器;SPDAT是SPI数据寄存器;AUXR1是辅助寄存器,作用是将P1口的SPI功能设置在P4口上,本实验中并未用到,不做介绍。
(1)SPCTL:SPI控制寄存器
最高位SSIG是来确定单片机是主机还是从机,其确定工作模式为主机或从机需要和第四位MSTR以及SS引脚相互配合以决定工作模式。
SPEN为SPI使能位,当SPEN=1时单片机才会使用SPI功能模块。
DORD用于设定SPI数据接受和发送的位顺序,当DORD=1,数据字的最低位先发送;当DORD=0,数据字的最高位先发送。
MSTR是用于单片机工作模式的选择位,与SSIG、SS引脚配合。
CPOL用于控制SPI时钟极性,CPOL=1,时钟位高电平空闲;CPOL=0,时钟为低电平空闲。
CPHA是时钟相位选择位,CPHA=1,数据在SPICLK的前时钟沿驱动,并在后时钟沿采样。CPHA=0,数据在SS为低(SSIG=00)时被驱动,在SPICLK的后时钟沿被改变,并在前时钟沿被采样。CPHA用来选择数据与时钟的配合模式,其模式如下:
SPR1与SPR0则是用于选择SPI时钟的速率:
工作模式选择表如下:
(2)SPSTAT是SPI状态寄存器
SPIF为通讯完成的标志位,每当完成一次传输,该位会被硬件置1,为了重置这个状态,以便SPI可以进行下一次的数据传输,我们需要通过软件将该位写为1,使得硬件回到0状态,从而准备下一次的传输,这种操作称为“写1清零”。“写1清零”是指对特定的寄存器位写入值1,以清除(清零)该位。这是一种常见的寄存器操作,用于清除中断标志位或状态寄存器中的某些标志位。
WCOL为写冲突,在数据传输的过程中如果对SPI数据寄存器SPDAT执行写操作,WCOL将置位,同样是写1清零。
(3)SPDAT是SPI数据寄存器
该寄存器用于存放进行SPI传输的数据。
本实验中通过对单片机I/O口进行编程,模拟SPI模块的功能并匹配MCP41010的时序逻辑以实现对数字电位器进行写入(具体见代码)。
3.LCD1602
(1)LCD1602常用命令介绍
LCD1602是一种字符型显示器,下图为其引脚图:
1、2、3引脚用来提供地、电源与调节显示对比度。4、5、6引脚用来调整其工作状态,E端为使能,1为数据有效,下降沿执行命令。7-14引脚是数据引脚。
下图为LCD1602常用命令及对应的引脚时序。


上图为LCD1602常用的命令。下图为常用命令的具体功能。

液晶显示模块是一个慢显示器件,所以在执行每条指令之前一定要确认模块的忙标志BF为低电平,表示不忙,否则需要等待。要显示字符时要先输入显示字符地址,也就是告诉模块在哪里显示字符,下图是lcd1602的内部显示地址。

LCD1602的工作流程通常为:

在使用LCD1602时,有以下常用命令:
|
初始化函数 |
|
void initial(void) //初始化函数 { write_command(0x38); delay(1); write_command(0x0c); delay(1); write_command(0x06); delay(1); write_command(0x01); delay(1); } |
|
这个里面进行了清屏、设置了两行显示、设置了光标自动右加一,这个函数通常在开机运行时或者要改变显示模式与内容时运行。 |
|
读忙函数 |
|
void check_busy(void) //读忙函数 { uchar time; do { time=0xff; E=0; RS=0; RW=1; E=1; time=out; } while(time&0x80); E=0; } |
|
用于检测LCD1602是否已经写入了上次发送的数据,等在其结束上一次写入再开始之后的操作。 |
|
写命令函数 |
|
void write_command(uchar com) //写命令函数 { check_busy(); E=0; RS=0; RW=0; out=com; E=1; _nop_(); E=0; delay(1); } |
|
在初始化时,会多次运用,写入状态,也在写入数据前运行用于确定数据写入的位置。 |
|
写数据函数 |
|
void write_data(unsigned int dat) //写数据函数 { check_busy(); E=0; RS=1; RW=0; out=dat; E=1; _nop_(); E=0; delay(1); } |
|
通常是写入字符的ASCII码或者是自定义字符的所在CGRAM中的位置。 |
(2)LCD1602自定义字符的写入和显示
构造LCD1603自定义字符需要先将其写入到CGRAM中,这个是LCD1602提供的一个可以自定义字符的库,显示时再将这里的数据写入DDRAM进行显示。

由上图和前面常用命令中的命令7可以得知,CGRAM中的可用字节地址为0x40-0x7F,共64个字节,每个字符占8个字节,并且最多只能存放8个自定义字符。
将数据写入CGRAM需要调用命令7的内容,首先确定要写入CGRAM中的地址,接着将数据写入CGRAM,即先设定要写入CGRAM字符数据的起始地址,再写入在该地址下的数据内容,每次写入一个字节,内部字节地址自动增加。每次写完8个字节后,再将地址设置到写一个字符要写入的初始地址,循环此过程。
对于自定义字符的显示,需要使用写数据函数,调用字符库中第一列CGRAM的索引数据来选择显示自己写入的自定义字符。例如:加点数字写入为 0 1 2 3 4 5,与CGRAM所在列的高四位与低四位组合形成的0 1 2 3 4 5这一索引数据对应,若在写数据函数中将索引数据5写入,则可将其对应的加点数字5由P0口输出。这里索引数据5为十进制,在字符库中索引为8位16进制0000 0101(正好是加点数字5这一字符对应的ASCII码)。写数据函数是通过显示字符对应的ASCII码找到字符库中对应的字模并对其进行输出。(通俗来说,给写数据函数的是地址,这一函数输出的是地址对应的图形)下图为这一过程的解释图:

三、实验内容与现象解释(各位最想知道的电路板怎么焊doge)
(1)可变增益放大器实验
|
电路设计图 |
| 电路板正面 |
![]() |
| 电路板背面 |
![]() |
| 实验接线图 |
![]() |
在本实验中设计的放大倍数为-2和-4,反相放大电路中反馈电阻为10kΩ数字电位器输入电阻设置为5kΩ和2.5kΩ,对应代码为0x80和0x40。代码如下。
|
IO口模拟SPI代码 |
|
#include<reg52.h> #define uchar unsigned char #define uint unsigned int sbit CS=P1^4; sbit SCLK=P1^7; sbit MOSI=P1^5; uchar num1[8]={0,0,0,1,0,0,0,1}; //0X11 uchar num3[8]={0,1,0,0,0,0,0,0}; //0x40 总电阻四分之一 2.5k uchar num8[8]={1,0,0,0,0,0,0,0}; //0X80 总电阻一半 5k uchar count,flag; void send(uchar num[8]); void delay(uchar j) //延时函数 { uchar i; i=125; for(;j>0;j--) { while(i--) { ; } } } void main() { TR0=0; TMOD=0x01; TH0=0x4b; //定时器工作方式1,16位,定时5秒,11.05926晶振 //为50ms重复100次,通过计算得到初值为19455 TL0=0xff; EA=1; //中断总开关打开 ET0=1; //定时器T0的中断打开 count=100; while(1) { flag=1; send(num3); TR0=1; while(flag); //while(flag)用来等待计时满5s //计时满5s以后会将flag=0,并且关闭定时器 flag=1; send(num8); TR0=1; while(flag); } } void timer0() interrupt 1 //定时器T0,中断号1
{ TH0=0x4b; TL0=0xff; count--; if(count<=0) { flag=0; count=100; TR0=0; } } void send(uchar num[8]) //发送两个字节的数据 { uchar t; CS=1; SCLK=0; delay(5); CS=0; //chip select 芯片低电平选中 delay(2); for (t=0;t<8;t++) //发送命令字节 { MOSI=num1[t]; delay(2); SCLK=1; delay(2); SCLK=0; } for (t=0;t<8;t++) //发送数据字节 { MOSI=num[t]; delay(2); SCLK=1; delay(2); SCLK=0; } delay(2); CS=1; } |
实验现象图如下:(设定峰峰值输入为1VPP)
|
增益为-1.89 |
增益为-3.62 |
![]() | ![]() |
本次实验设计放大倍数分别为-2和-4,实现显示结果为-1.89和-3.62,与理论之差异较大,针对此现象进行分析,我们重新测定了在上述代码运行情况下的输入电阻变化情况以及电位器总电阻的情况,见下图:
|
输入电阻(增益-2) |
输入电阻(增益-4) |
电位器总电阻 |
![]() | ![]() | ![]() |
从上图中可以看到,当正常工作情况下,输入电阻分别为2.74kΩ和5.4kΩ,与设计的电阻2kΩ和5kΩ并不符合,并且总电阻也变为了10.8kΩ,与10kΩ存在一定的偏差。
同时,根据代码中0x80和0x40可知,设计的电阻输入电阻分别为总电阻的二分之一和四分之一,在上述实测后,发现2.74和5.4正好为10.8的四分之一和二分之一,也进一步验证了代码程序的正确性。
在上述实测过后,再次计算在该条件下的反相放大器的增益:

这与实验中所得到的真实放大倍数-1.89和-3.62接近,从而验证了该实验的正确操作,考虑到器件存在偏差,实验结果与实测后的理论计算相同。
2)思考题。结合教材中提供的各类功能电路,能否设计出一种根据输入低频信号的幅度,自动调节放大倍数的可变增益放大电路?也就是说,当输入低频信号的幅度较小时,增益比较大;当输入低频信号的幅度较大时,增益比较小。从而确保输出不至于饱和。说说你的设计思路、实现方法。(低频信号频率较低可视为直流)
1.设计思路分为以下几个部分:
信号检测:首先需要检测输入信号的幅度,可以通过整流和滤波电路实现,将交流输入信号转换为与其幅度成比例的直流电平。
控制机制:设计一个能够响应上述直流电平变化的控制机制,该机制将根据检测到的信号幅度调整放大器的增益。
放大电路:选择或设计一个可变增益放大器。这种放大器的增益可以由外部控制信号调节。
反馈系统:建立一个反馈系统,将输出信号的幅度反馈到控制机制,形成闭环控制,以实现更稳定的增益调节。
保护机制:设计过载保护机制,防止输出信号饱和。
2.上述设计思路对应的实现方法:
输入信号整流:使用二极管桥将输入交流信号转换为脉动直流信号。
直流电平检测:通过RC低通滤波平滑整流后的脉冲直流信号,得到与输入信号幅度成比例的直流电平。
增益控制:利用直流电平去控制一个可变电阻器或使用模拟开关来改变放大器的反馈网络,从而改变增益。
可变增益放大器:选择一个具有可变增益的运算放大器,如使用外部电阻设定增益的运放,或使用专用的可变增益放大电路的芯片。
反馈网络:设计反馈网络,使放大器的增益与输入信号幅度成反比,可以通过使用受直流电平控制的电阻值来实现。
输出保护:在放大器的输出端加入限幅电路,以防止输出信号过大导致饱和。
(闭环控制:可能需要一个微控制器或逻辑电路来动态调整增益设置,确保输出信号始终处于合适的电平。)
(2)LCD1602液晶显示模块驱动程序的仿真运行。
利用PROTEUS搭建的仿真电路如下:

Proteus中单片机不用复位电路供电与晶振不妨碍进行仿真,所以没有接。这个原理图的线路接法与本人的开发板是一致的,在仿真的基础上同时本人对代码也进行了实物的验证。烧录工具以及对应现象如下:
|
烧录工具 |
![]() |
|
学号姓名显示 |
加点数字学号显示 | |
|
仿真 | ![]() | ![]() |
|
实物图 | ![]() | ![]() |
姓名与学号显示
|
代码 |
|
#include<reg52.h> #include<intrins.h> #define uchar unsigned char #define uint unsigned int #define out P0 sbit RS=P2^6; sbit RW=P2^5; sbit E=P2^7; void delay(uint j); void check_busy(void); void write_command(uchar com); void write_data(uchar dat); void initial(void); void string(uchar ad,uchar *s); void main() //主函数 { initial(); //初始化 delay(100); string(0x80,"你的名字的拼音"); //在第一行第一个字符处写入姓名拼音全拼 自行替换 delay(1); string(0xC0,"你的学号 "); //在第二行第一个字符处写入学号 自行替换 while(1); } void delay(uint j) //延时函数 { uchar i=250; for (;j>0;j--) { while(--i); i=249; while(--i); i=250; } } void check_busy(void) //读忙函数 { uchar dt; do { dt=0xff; E=0; RS=0; RW=1; E=1; dt=out; }while(dt&0x80); E=0; } void write_command(uchar com) //写命令函数 { check_busy(); E=0; RS=0; RW=0; out=com; E=1; delay(1); E=0; delay(1); } void write_data(uchar dat) //写数据函数 { check_busy(); E=0; RS=1; RW=0; out=dat; E=1; delay(1); } void initial(void) //初始化函数 { write_command(0x3f); delay(1); write_command(0x0c); delay(1); write_command(0x06); delay(1); write_command(0x01); delay(1); } void string(uchar ad,uchar *s) //写字符函数 { write_command(ad); while(*s>0) { write_data(*s++); delay(1); } } |
效果显示如下:这里为避免个人信息泄露,就不展示了。请自行操作。
加点学号显示
在这一部分需要将自定义字符写入到CGRAM中并显示在DDRAM上,首先确定各字模的建立,根据前面的讲述,每一个字模占8个字节,即8行,每一个行中有8位,其中格子点亮为1,不点亮为0,前三列的格子不使用,用于对CGRAM地址的识别,只是用后五个格子进行建立的字模,过程如下:(DEC为十进制数,HEX为16进制数)
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
(涉及到自己的学号数字 请自己去定义相应的字模)
此处需要注意定义的数据要存储在ROM里面,写入无法更改,其大小为64KB,足够存储数据。而单片机的RAM有限,所以尽量把不变的数组都存在ROM中。
程序可以简单的分为code(程序)区,和data (数据)区,code区在运行的时候是不可以更改的,所以要存放在ROM区。data区存放全局变量和临时变量,需要不断的改变,所以要存放在RAM中。CPU从code区读取指令,对data区的数据进行运算处理。
c51中的存储类型:
code :程序存储区(64KB),
data :可直接寻址的内部数据存储区(128B)
idata:不可直接寻址的内部数据存储区(256B)
bdata:可位寻址内部数据存储区(16B)
xdata:外部数据存储区(64KB)
pdata:分页的外部数据存储区
|
代码 |
|
#include<reg52.h> #include<intrins.h> #define uchar unsigned char #define uint unsigned int #define out P0 sbit RS=P2^6; sbit RW=P2^5; sbit E=P2^7; void check_busy(void); void write_command(uchar com); void write_data(uint dat); void initial(void); void delay(uint); uchar i,j,wei; uchar code num[48]={ 14,17,17,17,17,14,0,4, 4,28,12,4,4,31,0,4, 31,1,31,16,16,31,0,4, 31,1,31,1,1,31,0,4, 18,18,18,31,2,2,0,4, 31,16,31,1,1,31,0,4 }; //数字0-5加点的字模 uchar code mynum[11]={1,1,1,1,1,1,1,1,1,1,1}; //学号 对应CGRAM那一列的索引 正好对应写入的自建字符加点数字0123 上面的1请替换为自己的学号 void main(void) { initial(); //初始化 delay(1); i=6;//写入6个字模 一共最多只能写8个字模 wei=0x40; //CGRAM的起始地址 while(i) //在自建库中输入字模 一个字模占用8个字节 { write_command(wei); delay(1); for (j = 0; j < 8; j++) //分别写入每个字节的数据 一个字节对应一行 8行 { write_data(num[(6-i)*8+j]); delay(1); } wei=wei+8;//每写入一个字模后地址新增加8位至下一个字模的起始地址 i--; } while (1) { write_command(0x82); //向第一行第二个字符处开始写入 delay(1); i=11; while(i) { write_data(mynum[11-i]); delay(1); i--; } write_command(0xc2); //向第二行第二个字符处开始写入 delay(1); i=11; while(i) { write_data(mynum[11-i]); delay(1); i--; } } } void delay(uint j) //延时函数 { uchar i=250; for(;j>0;j--) { while(--i); i=249; while(--i); i=250; } } void check_busy(void) //读忙函数 { uchar time; do { time=0xff; E=0; RS=0; RW=1; E=1; time=out; } while(time&0x80); E=0; } void write_command(uchar com) //写命令函数 { check_busy(); E=0; RS=0; RW=0; out=com; E=1; _nop_(); E=0; delay(1); } void write_data(unsigned int dat) //写数据函数 { check_busy(); E=0; RS=1; RW=0; out=dat; E=1; _nop_(); E=0; delay(1); } void initial(void) //初始化函数 { write_command(0x3f); delay(1); write_command(0x0c); delay(1); write_command(0x06); delay(1); write_command(0x01); delay(1); } |
效果显示如下:这里为避免个人信息泄露,就不展示了。请自行操作。
滚动显示
在这一部分中,通过使用LCD1602的命令5,可实现移动字符的功能,达到滚动显示的效果。代码及效果如下:
|
代码 |
|
#include<reg52.h> #include<intrins.h> #define uchar unsigned char #define uint unsigned int #define out P0 sbit RS=P2^6; sbit RW=P2^5; sbit E=P2^7; void delay(uint j); void check_busy(void); void write_command(uchar com); void write_data(uchar dat); void initial(void); void string(uchar ad,uchar *s); void main() //主函数 { initial(); //初始化 delay(100); string(0x80,"你的名字的拼音"); //在第一行第一个字符处写入姓名英文全拼 自行替换 delay(1); string(0xC0,"你的学号 "); //在第二行第一个字符处写入学号 自行替换 while(1) { write_command(0x18); //通过移动字符实现滚动效果 delay(100); }; } void delay(uint j) //延时函数 { uchar i=250; for (;j>0;j--) { while(--i); i=249; while(--i); i=250; } } void check_busy(void) //读忙函数 { uchar dt; do { dt=0xff; E=0; RS=0; RW=1; E=1; dt=out; }while(dt&0x80); E=0; } void write_command(uchar com) //写命令函数 { check_busy(); E=0; RS=0; RW=0; out=com; E=1; delay(1); E=0; delay(1); } void write_data(uchar dat) //写数据函数 { check_busy(); E=0; RS=1; RW=0; out=dat; E=1; delay(1); } void initial(void) //初始化函数 { write_command(0x3f); delay(1); write_command(0x0c); delay(1); write_command(0x06); delay(1); write_command(0x01); delay(1); } void string(uchar ad,uchar *s) //写字符函数 { write_command(ad); while(*s>0) { write_data(*s++); delay(1); } } |
|
显示效果 | ![]() |
四、实验误差
1.在可变增益放大电路的实验中,由于芯片中的电位器阻值与标准阻值存在偏差造成了一定的实验误差。
2.在对单片机编写程序中,要求定时器计时时间位5s,求定时器初值的公式位:

得X=19455.75,而编程中直接取19455进行赋初值,所以增益的变化并不是准确的5s。
五、实验总结
请自行总结。例如:熟悉了什么软件操作?时序控制?对于LCD1602的理解?
更多推荐

























所有评论(0)