基于STM32的四种波形发生器控制设计
信号发生器在生产实践和科技领域中有着广泛的应用,各种波形曲线均可以用三角函数方程式来表示。能够产生多种波形,如三角波、锯齿波、矩形波(含方波)、正弦波的电路被称为函数信号发生器。函数信号发生器在电路实验和设备检测中具有十分广泛的用途,例如在通信、广播、电视系统中,都需要射频(高频)发射。这里的射频波就是载波,把音频(低频)、视频信号或脉冲信号运载出去,就需要能够产生高频的振荡器。在工业、农业、生物
系统简介
信号发生器在生产实践和科技领域中有着广泛的应用,各种波形曲线均可以
用三角函数方程式来表示。能够产生多种波形,如三角波、锯齿波、矩形波(含
方波)、正弦波的电路被称为函数信号发生器。函数信号发生器在电路实验和设
备检测中具有十分广泛的用途,例如在通信、广播、电视系统中,都需要射频(高
频)发射。这里的射频波就是载波,把音频(低频)、视频信号或脉冲信号运载出去,就需要能够产生高频的振荡器。在工业、农业、生物医学等领域内,如高
频感应加热、熔炼、淬火、超声诊断、核磁共振成像等,都需要功率或大或小、
频率或高或低的振荡器。
函数信号发生器是各种测试和实验过程中不可缺少的工具,在通信、测量、
雷达、控制、教学等领域应用十分广泛。不论是在生产、科研还是教学上,信号
发生器都是电子工程师信号仿真实验的最佳工具。 而且,信号发生器的设计方法多,设计技术也越来越先进, 随着我国经济和科技的发展, 对相应的测试仪器和测试手段也提出了更高的要求,信号发生器己成为测试仪器中至关重要的一类,因此开发信号发生器具有重大意义。
1. 设计任务
结合实际情况,基于STM32F103单片机设计一个四种波形发生器(正弦波、方波、三角波、锯齿波)。该系统应满足的功能要求为:
(1) 可以实现四种波形:正弦波、方波、三角波、锯齿波;
(2) 通过按键进行选择,频率可以调整;
(3) LCD液晶显示;
(4)设计出来之后用Proteus软件仿真出效果;
主要硬件设备:STM32F103单片机、DAC0832数模转换芯片、矩阵键盘、LCD12864液晶屏幕。
注:这一部分需要写明系统功能需求,用到的主要硬件(参考实验箱的说明书)。
2. 整体方案设计
四种波形发生器以STM32F103单片机作为整个系统的控制核心,应用其强大的处理速度,构成波形发生器系统。该系统具备将数字信号转换为模拟信号的能力。正弦波可以直接采用数学函数sin计算出来,送入单片机进行数据处理。经单片机运算后的数据送入DAC0832芯片将数字信号转换为模拟信号输出。其他的波形都可以采用自身的规律采用不同的算法实现。
图2-1 基于STM32单片机的四种波形发生器原理图
本系统硬件主要由矩阵键盘、D/A转换器、LCD12864显示系统、处理器等几部分组成。各模块的主要功能如下:
(1)矩阵键盘的功能是设置波形和频率,然后送入单片机。
(2) MCU的功能是识别键盘的数据并进行相对应的处理,然后转换出波形的数字信号和LCD显示的数据。
(3) LCD12864显示系统的功能是将设置的波形和频率显示出来。
系统的整体设计方案设计图如图2-2所示。
图2-2 系统的整体方案设计图
注:文中出现的所有框图、流程图都要用VISIO画,不允许从文档中裁剪然后粘贴。要注意图的格式,图中的文字大小不要大于正文,图题用五号宋体,居中。
3. 系统硬件电路设计
3.1 时钟电路
1.什么是时钟
时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。时钟系统就是CPU的脉搏,决定cpu速率,像人的心跳一样 只有有了心跳,人才能做其他的事情,而单片机有了时钟,才能够运行执行指令,才能够做其他的处理 (点灯,串口,ADC),时钟的重要性不言而喻。
为什么 STM32 要有多个时钟源呢?
STM32本身十分复杂,外设非常多 但我们实际使用的时候只会用到有限的几个外设,使用任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费 并且,同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题。所以便有了STM32的时钟系统和时钟树
总括:
STM32时钟系统主要的目的就是给相对独立的外设模块提供时钟,也是为了降低整个芯片的耗能。系统时钟,是处理器运行时间基准(每一条机器指令一个时钟周期),时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。一个单片机内提供多个不同的系统时钟,可以适应更多的应用场合。不同的功能模块会有不同的时钟上限,因此提供不同的时钟,也能在一个单片机内放置更多的功能模块。对不同模块的时钟增加开启和关闭功能,可以降低单片机的功耗, STM32为了低功耗,他将所有的外设时钟都设置为disable(不使能),用到什么外设,只要打开对应外设的时钟就可以,其他的没用到的可以还是disable(不使能),这样耗能就会减少。这就是为什么不管你配置什么功能都需要先打开对应的时钟的原因。
时钟系统
1各个时钟源
STM32 有4个独立时钟源:HSI、HSE、LSI、LSE。
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
其中LSI是作为IWDGCLK(独立看门狗)时钟源和RTC时钟源 而独立使用,而HSI高速内部时钟,HSE高速外部时钟,LSI低速内部时钟,这三个经过分频或者倍频 作为系统时钟来使用。
PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。通过倍频之后作为系统时钟的时钟源,在这里我采用8M晶振,4倍频,32M。(STM32功能强,速度太快,对电脑性能要求高,所以我们尽量不要将频率调整太高)。
图3.1-1 时钟电路与单片机的接口电路
3.2 复位电路
- 复位的工作方式
复位电路的作用是为了是系统恢复到初始状态的,单片机的复位方式也是存在好几种的:上电复位,系统复位,备份区域复位
上电复位:其产生的条件是,当系统上电、掉电,以及系统从待机模式返回时,发生电源复位。电源复位能够复位除了备份区域寄存器之外的所有寄存器的状态。
系统复位:以下任一事件发生时,均能产生一个系统复位: - NRST引脚上的低电平(外部复位)
- 窗口看门狗计数终止(WWDG复位)
- 独立看门狗计数终止(IWDG复位)
- 软件复位(SW复位)
- 低功耗管理复位
系统复位能够复位除时钟控制寄存器CRS中的复位标志和备份区域中的寄存器之外的所有寄存器。
备份区域复位:对于备份区域的复位,一种是在软件复位的时候设定备份区域控制寄存器中的对应位产生的;另一种是当电源和电池都掉电又重新上电时产生的。
平常我们常用的复位方式有两种,一种是NRST引脚的低电平复位,通过按键复位电路给这个引脚一个低电平,让系统完成复位,另一种大家都知道,那就是上电复位了,有时候是复位电路莫名失效了,有时是刚启动的时候,虽然用的没有按键复位电路多,不过也算是很常用的一种复位方式了。
由图可以看出,当按键按下时,NRST引脚和地相接,从而被拉低,产生一个低电平信号,实现复位。当系统上电瞬间,电容充电导致NRST在上电瞬间被拉为低电平信号,这个信号持续11000us的时间。
图3.1-1 时钟电路与单片机的接口电路
时间计算公式:t = 1.1RC = 1.110000Ω0.000001F = 0.011s = 11000us
这个时间对于STM32的NRST所需要的低电平信号时长,足够了。
3.3 D/A转换器工作原理
本设计所用D/A转换电路采用通用的DAC0832数模转换芯片,DAC0832是8位的D/A转换集成芯片。与微处理器完全兼容。这个DA芯片以其价格低廉、接口简单、转换控制容易等优点,在单片机应用系统中得到广泛的应用。D/A转换器由8位输入锁存器、8位DAC寄存器、8位D/A转换电路及转换控制电路构成。
任D/A是将数字量转换成模拟量的器件,D/A的输出是电压或电流信号。衡量D/A性能的主要参数是:
分辨率:即输出模拟量的最小变化量。
满刻度误差:即输入为全1时,实际输出电压与理想值之差。
转换时间:从转换器的输入改变到输出稳定的时间间隔。
电流稳定时间:1us
注意:
使用DAC0832应该注意DAC0832是电流型输出,在应用时外接运放
使之成为电压型输出。
根据对DAC0832的数据锁存器和DAC寄存器的不同的控制方式,DAC0832有三种工作方式:直通方式、单缓冲方式和双缓冲方式。
1、单缓冲方式。单缓冲方式是控制输入寄存器和DAC寄存器同时接收资料,或者只用输入寄存器而把DAC寄存器接成直通方式。此方式适用只有一路模拟量输出或几路模拟量异步输出的情形。
2、双缓冲方式。双缓冲方式是先使输入寄存器接收资料,再控制输入寄存器的输出资料到DAC寄存器,即分两次锁存输入资料。此方式适用于多个D/A转换同步输出的情节。
3、直通方式。直通方式是资料不经两级锁存器锁存,即 CS*,XFER* ,WR1* ,WR2* 均接地,ILE接高电平。此方式适用于连续反馈控制线路和不带微机的控制系统,不过在使用时,必须通过另加I/O接口与CPU连接,以匹配CPU与D/A转换。
DAC0832引脚功能电路应用原理图DAC0832是采样频率为八位的D/A转换芯片,集成电路内有两级输入寄存器,使DAC0832芯片具备双缓冲、单缓冲和直通三种输入方式,以便适于各种电路的需要(如要求多路D/A异步输入、同步转换等)。所以这个芯片的应用很广泛,关于DAC0832应用的一些重要资料见下图: D/A转换结果采用电流形式输出。若需要相应的模拟电压信号,可通过一个高输入阻抗的线性运算放大器实现。运放的反馈电阻可通过RFB端引用片内固有电阻,也可外接。DAC0832逻辑输入满足TTL电平,可直接与TTL电路或微机电路连接。
D/A转换
D/A芯片DAC0832的应用:DAC0832是8位 D/A芯片,片内带数据锁存器,电流输出。该系列产品包括DAC0830——32,它们可以完全相互代换。它由8位输入锁存器、8位DAC寄存器、8位D/A转换电路及转换控制电路组成
ADC0808引脚如图,各引脚功能如下:
DI0-DI7:数据输入线,TLL电瓶。
ILE:数据锁存允许控制信号输入线,高电平有效。
CS:片选信号输入线,低电平有效。
WR1:为输入寄存器的写选通信号。
XFER:数据传送控制信号输入线,低电平有效。
WR2:为DAC寄存器写选通输入线。
Iout1:电流输出线。当输入全为1时Iout1最大。
Iout2:电流输出线。其值与Iout1之和为常数。
Rfb:反馈信号输入线,芯片内部有反馈电阻。
Vcc:电源输入线(+5v~+15v)
Vref:基准电压输入线(-10v~+10v)
AGND:模拟地,模拟信号和基准信号参考地。
DGND:数字地,两种地线在基准电源处共地比较好。
其与单片机的接口电路如图3-1所示。
图3-1 DAC0832与单片机的接口电路
3.4矩阵键盘电路
- 矩阵键盘的工作方式
对键盘的响应取决于键盘的工作方式,键盘的工作方式应根据实际应用系统中的CPU的工作状况而定,其选取的原则是既要保证CPU能及时响应按键操作,又不要过多占用CPU的工作时间。通常键盘的工作方式有三种,编程扫描、定时扫描和中断扫描。
(1)编程扫描方式
编程扫描方式是利用CPU完成其它工作的空余时间,调用键盘扫描子程序来响应键盘输入的要求。在执行键功能程序时,CPU不再响应键输入要求,直到CPU重新扫描键盘为止。
(2)定时扫描方式
定时扫描方式就是每隔一段时间对键盘扫描一次,它利用单片机内部的定时器产生一定时间(例如10ms)的定时,当定时时间到就产生定时器溢出中断。CPU响应中断后对键盘进行扫描,并在有按键按下时识别出该键,再执行该键的功能程序。
(3)中断扫描方式
上述两种键盘扫描方式,无论是否按键,CPU都要定时扫描键盘,而单片机应用系统工作时,并非经常需要键盘输入,因此,CPU经常处于空扫描状态。
为提高CPU工作效率,可采用中断扫描工作方式。其工作过程如下:当无按键按下时,CPU处理自己的工作,当有按键按下时,产生中断请求,CPU转去执行键盘扫描子程序,并识别键号。
- 为了保证波形尽量不受到干扰的同时尽量提高电脑的仿真效果和速度(STM32功能强,速度太快,对电脑性能要求高,所以我们尽量不要同时给单片机分配太多任务)我们采用编程扫描方式,开启波形后关闭键盘,开启键盘的时候关闭波形。
4x4矩阵键盘的连接如图3-2所示。
图3-2 4x4矩阵键盘的连接图
3.5 LCD12864液晶显示电路
- 矩阵键盘的工作方式
LCD12864支持两种模式驱动,分别是并行模式和串行模式,通过PSB引脚进行选择模式,并行数据传输速度快,但是占用引脚多,反之串行模式,传输速度相对并行模式而言比较慢,但是省引脚。
串行模式驱动LCD12864
说明:
GND——GND
VCC——VCC(5V or 3.3V)
V0 ——空(或者接一个可变电阻到VCC)
RS ——接PB14 RS = 0 命令 RS = 1 数据
RW ——接PB13 RW = 0 写 RW = 1 读
E ——接PB15
DB0-DB7 ——数据
PSB——接GND,PSB = 0 串行模式 PSB = 1 并行模式
RST——复位
BLA——VCC(5V or 3.3V)
BLK——接GND
剩余引脚可以不接,留空
注意:在串行模式下,一个完整的串行传输周期由以下组成:
首先送入启动字节,送入连续的5个“1”来启动一个周期此时传输计数器被重置,并且串行传输被同步。紧接的两个位指定传输方向(RW,1是读数据,0是写数据)以及传输性质(RS,0是命令寄存器,1是数据寄存器),最后第八位是固定的“0”。
所以写指令之前,必须先传输 11111 000 (即0xF8);
写数据之前必须先传输 11111 010 (即0xFA);
传输完启动字节后,开始传输指令或者数据,指令或者数据都是以字节为单位,一个字节8bit,在传输过程中会进行拆分处理,如传输0x35(二进制为 0011 0101)时:
将0x35的高4位放在第一个字节的高4位,低4位补0,如 0011 0000
将0x35的低4位放在第二个字节的高4位,低4位补0,如 0101 0000
所以在传输指令或者数据时,会将一个字节拆分为两个字节进行传输 0011 0000 0101 0000
假设此时传输的0x35是数据而非指令,由于在代码中将启动字节与数据进行合并,所以传输的数据为:0x00FA3050
为了提高仿真速度我们采用的是并行通信模式。
图3.1-1 LCD12864显示电路与单片机的接口电路
注:以上各部分按照电路功能模块化介绍器件选型、器件特性、电路设计思路、电路功能等,要注意与第2小节中的整体方案设计图相对应。文中出现的电路原理图同样不能从资料中裁剪,可从自己设计的Proteus图中拷贝。
4. 系统程序设计
4.1 主程序流程图
对系统运行工作流程进行说明后,给出系统主程序流程。
设计思路:首先将所有配置进行初始化,为了消除干扰和提升仿真效率,我们将系统的工作状态分为两种,一种为调整状态,另一种为波形输出状态,使用一个开关的通断来调整。
我们采用的是DAC0832芯片来做DA转换的,DAC0832将输出电压分成了0xFF(255)份,需要输出不同的波形我们需要给不同的数据,在这里我将所有的波形的一个周期分成了100份,定时器每隔一段时间中断一次,中断100次为一个周期。
正弦波采用数学计算公式sin来计算;
方波只需要在定时器前面50次给0,后面50次给最大值即可;
三角波只需要在定时器前面50次采用最大值的50分之一乘于它本身,后面50次相反即可;
锯齿波只需要在一个周期内,定时器中断一次就用他本身乘于电压最大值的100分之一即可;
DA转换基本控制系统原理如图4-2所示。
图4-1 主程序流程图
5. 系统调试
这一部分分为Proteus软件仿真调试和硬件调试两部分,分别给出调试过程、调试时应注意的问题,分析遇到的问题及问题的解决方法,Proteus调试可截屏运行结果图形并加以分析。
5.1 Proteus软件仿真调试
正弦波
方波
三角波
锯齿波
5.2 硬件调试
在实际电路中,我们可以将频率调整的快一点,但是要注意抗干扰问题,毕竟上面的都是理论值,模拟信号非常容易受到其他因素的干扰。所有配置,都必须跟程序里面完全一致,如果波形失真,或者频率不对,还应进行相对应的补偿。
## 6. 程序清单
#include "stm32f10x.h"
#include "sys.h"
#include "delay.h"
#include "12864.h"
#include "key4_4.h"
#include "timer.h"
#define KEY0 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_10)//读取按键0
void Delay_Ms(u16 time);
/*************** 配置Switch用到的I/O口 *******************/
void Init_GPIO_Switch(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
//关闭jtag,使能SWD,可以用SWD模式调试
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
// 使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
//PC0
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//IO口速度为50MHz
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
//设置成输入
GPIO_Init(GPIOB, &GPIO_InitStructure);
//初始化PC0
}
/****************************************************************
*功能名称:main
*描述:主程序。
*输入:无
*输出:无
*返回:无
****************************************************************/
int main(void)
{
u8 i=0;
RCC_ClocksTypeDef RCC_Clocks; //初始化程序
RCC_Configuration(RCC_PLLMul_4); //8M*4 == 32M
RCC_GetClocksFreq(&RCC_Clocks); //获取片上时钟
Init_12864(); //初始化12864液晶
Key_Init();
Init_GPIO_Switch();
Init_GPIO_DAC0832();
Data0=25;
TIM3_Int_Init(50+Data0,320);
//频率:32000000/ 320 ==100 000 /100 == 1000 /50==20
LCD_P6x8Str(3,16," Sine Wave ");
LCD_P6x8Str(7,6*2,"Frequency: 15 Hz");
while (1)
{
if(KEY0)
{
if(i!=2)
{
__set_PRIMASK(1);
GPIO_ResetBits(GPIOB, ((uint16_t)0xC000));
}
Key_Test();
i=2;
}
else{
if(i!=5)
{
TIM3_Int_Init(50+Data0,320);
__set_PRIMASK(0); //使能TIMx外设
GPIO_ResetBits(GPIOB, ((uint16_t)0xC000));
}
i=5;
}
}
}
/*************** 定时器 *******************/
#include "timer.h"
#include "math.h"
#define ADC_CS_WR(a) if (a) \
GPIO_SetBits(GPIOB,GPIO_Pin_14);\
else \
GPIO_ResetBits(GPIOB,GPIO_Pin_14)
#define ADC_CS_WR2(a) if (a) \
GPIO_SetBits(GPIOB,GPIO_Pin_15);\
else \
GPIO_ResetBits(GPIOB,GPIO_Pin_15)
#define PI 3.1415926f //圆周率
u8 mode; //模式:正弦波……
u16 freq; //频率
u8 time; //计次参数
u8 AM; //调幅
/*************** 配置DAC用到的I/O口 *******************/
void Init_GPIO_DAC0832(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); // 使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = ((uint16_t)0x03FF); //选择对应的引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化PC端口
GPIO_SetBits(GPIOC, ((uint16_t)0x00FF)); // 高
GPIO_ResetBits(GPIOC, ((uint16_t)0x00FF)); // 低
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 使能PC端口时钟
GPIO_InitStructure.GPIO_Pin = ((uint16_t)0xC000); //选择对应的引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PC端口
GPIO_SetBits(GPIOC, ((uint16_t)0xC000)); // 高
GPIO_ResetBits(GPIOC, ((uint16_t)0xC000)); // 低
time=0;
ADC_CS_WR(0);
mode=0; //默认输出正弦波
freq=100; //默认频率
AM=255; //最大幅度
}
void DAC_0832_Data(uint8_t Data)
{
GPIO_ResetBits(GPIOC,~Data);
GPIO_SetBits(GPIOC,Data);
}
void sine_wave(u8 location)//输出正弦波
{
double x=(double)location/50*PI;//把0-100放缩到0-2派(pai,没有那个符号)
u8 y=(sin(x)*(AM/2)+(AM/2));//算出y,并放缩到0-254(因为ADC范围0-AM,芯片落后)
DAC_0832_Data(y);
}
void squ_wave(u8 location)//方……
{
if(location<50)
DAC_0832_Data(AM);
else
DAC_0832_Data(0);//这个简单
}
void tri_wave(u8 location)//三……
{
//为了简化,在单周期输出V字形
u8 y;
if(location<50)
y=(50-location)*AM/50;
else
y=(location-50)*AM/50;
DAC_0832_Data(y);
//偶函数,当然说奇函数也没错
}
void saw_wave(u8 location)//锯……
{
DAC_0832_Data(location*AM/100);
//用(100-location)也以变成反向锯齿
}
//通用定时器中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 计数到5000为500ms
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_ITConfig( //使能或者失能指定的TIM中断
TIM3, //TIM3
TIM_IT_Update ,
ENABLE //使能
);
// TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx外设
// TIM_Cmd(TIM3, DISABLE);
}
void TIM3_IRQHandler(void) //TIM3中断
{
switch(mode)
{
case W_SINE:sine_wave((u8)(time*freq/100)%100);break;//计算出波的位置
case W_SQU:squ_wave((u8)((time*freq/100)%100));break;
case W_TRI:tri_wave((u8)((time*freq/100)%100));break;
case W_SAW:saw_wave((u8)((time*freq/100)%100));break;
}
time++;
if(time>=100)//计数100次
time=0;
}
#include "key4_4.h"
#include "delay.h"
#include "sys.h"
#include "12864.h"
#include "timer.h"
//8个引脚 4个为行 4个为列
//行输出端口定义
#define X1_GPIO_PORT GPIOB
#define X2_GPIO_PORT GPIOB
#define X3_GPIO_PORT GPIOB
#define X4_GPIO_PORT GPIOB
//列输入端口定义
#define Y1_GPIO_PORT GPIOB
#define Y2_GPIO_PORT GPIOB
#define Y3_GPIO_PORT GPIOB
#define Y4_GPIO_PORT GPIOB
//行输出引脚定义
#define X1_GPIO_PIN GPIO_Pin_0
#define X2_GPIO_PIN GPIO_Pin_1
#define X3_GPIO_PIN GPIO_Pin_2
#define X4_GPIO_PIN GPIO_Pin_3
//列输入引脚定义
#define Y1_GPIO_PIN GPIO_Pin_4
#define Y2_GPIO_PIN GPIO_Pin_5
#define Y3_GPIO_PIN GPIO_Pin_6
#define Y4_GPIO_PIN GPIO_Pin_7
//行输出时钟定义
#define X1_RCC RCC_APB2Periph_GPIOB
#define X2_RCC RCC_APB2Periph_GPIOB
#define X3_RCC RCC_APB2Periph_GPIOB
#define X4_RCC RCC_APB2Periph_GPIOB
//列输入时钟定义
#define Y1_RCC RCC_APB2Periph_GPIOB
#define Y2_RCC RCC_APB2Periph_GPIOB
#define Y3_RCC RCC_APB2Periph_GPIOB
#define Y4_RCC RCC_APB2Periph_GPIOB
//移植代码只需要修改上面的端口和引脚和时钟即可,下面的代码不用修改。
//矩阵键盘所用的8个引脚可连续可不连续,看实际需要和个人爱好自己定义。
unsigned char Y1,Y2,Y3,Y4;
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(X1_RCC|X2_RCC|X3_RCC|X4_RCC|Y1_RCC|Y2_RCC|Y3_RCC|Y4_RCC|RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
/*******************4行输出 *****************************/
GPIO_InitStructure.GPIO_Pin = X1_GPIO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(X1_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = X2_GPIO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(X2_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = X3_GPIO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(X3_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Pin = X4_GPIO_PIN ;
GPIO_Init(X4_GPIO_PORT, &GPIO_InitStructure);
/******************* 4列输入 *******************/
GPIO_InitStructure.GPIO_Pin = Y1_GPIO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(Y1_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = Y2_GPIO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(Y2_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = Y3_GPIO_PIN ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(Y3_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Pin = Y4_GPIO_PIN;
GPIO_Init(Y4_GPIO_PORT, &GPIO_InitStructure);
}
int Key_Scan(void)
{
uchar KeyVal;
GPIO_SetBits(X1_GPIO_PORT,X1_GPIO_PIN); //先让X1输出高
GPIO_SetBits(X2_GPIO_PORT,X2_GPIO_PIN); //先让X2输出高
GPIO_SetBits(X3_GPIO_PORT,X3_GPIO_PIN); //先让X3输出高
GPIO_SetBits(X4_GPIO_PORT,X4_GPIO_PIN); //先让X4输出高
if((GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN)|GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN)|GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN)|GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN))==0x0000)
return -1; //如果X1到X4全为零则没有按键按下
else
{
Delay_Ms(5); //延时5ms去抖动
if((GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN)|GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN)|GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN)|GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN))==0x0000)
return -1;
}
GPIO_ResetBits(X1_GPIO_PORT,X1_GPIO_PIN);
GPIO_ResetBits(X2_GPIO_PORT,X2_GPIO_PIN);
GPIO_ResetBits(X3_GPIO_PORT,X3_GPIO_PIN);
GPIO_SetBits(X4_GPIO_PORT,X4_GPIO_PIN);
Y1=GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN);Y2=GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN);
Y3=GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN);Y4=GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN);
if(Y1==1&&Y2==0&&Y3==0&&Y4==0)
KeyVal='*';
if(Y1==0&&Y2==1&&Y3==0&&Y4==0)
KeyVal=0;
if(Y1==0&&Y2==0&&Y3==0&&Y4==1)
KeyVal='D';
if(Y1==0&&Y2==0&&Y3==1&&Y4==0)
KeyVal='#';
while(((GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN))|(GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN))|(GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN))|(GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN))) > 0);
//等待按键释放
GPIO_SetBits(X1_GPIO_PORT,X1_GPIO_PIN);
GPIO_ResetBits(X2_GPIO_PORT,X2_GPIO_PIN);
GPIO_ResetBits(X3_GPIO_PORT,X3_GPIO_PIN);
GPIO_ResetBits(X4_GPIO_PORT,X4_GPIO_PIN);
Y1=GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN);Y2=GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN);
Y3=GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN);Y4=GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN);
if(Y1==1&&Y2==0&&Y3==0&&Y4==0)
KeyVal=1;
if(Y1==0&&Y2==1&&Y3==0&&Y4==0)
KeyVal=2;
if(Y1==0&&Y2==0&&Y3==1&&Y4==0)
KeyVal=3;
if(Y1==0&&Y2==0&&Y3==0&&Y4==1)
KeyVal='A';
while(((GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN))|(GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN))|(GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN))|(GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN))) > 0);
GPIO_ResetBits(X1_GPIO_PORT,X1_GPIO_PIN);
GPIO_SetBits(X2_GPIO_PORT,X2_GPIO_PIN);
GPIO_ResetBits(X3_GPIO_PORT,X3_GPIO_PIN);
GPIO_ResetBits(X4_GPIO_PORT,X4_GPIO_PIN);
Y1=GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN);Y2=GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN);
Y3=GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN);Y4=GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN);
if(Y1==1&&Y2==0&&Y3==0&&Y4==0)
KeyVal=4;
if(Y1==0&&Y2==1&&Y3==0&&Y4==0)
KeyVal=5;
if(Y1==0&&Y2==0&&Y3==1&&Y4==0)
KeyVal=6;
if(Y1==0&&Y2==0&&Y3==0&&Y4==1)
KeyVal='B';
while(((GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN))|(GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN))|(GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN))|(GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN))) > 0);
GPIO_ResetBits(X1_GPIO_PORT,X1_GPIO_PIN);
GPIO_ResetBits(X2_GPIO_PORT,X2_GPIO_PIN);
GPIO_SetBits(X3_GPIO_PORT,X3_GPIO_PIN);
GPIO_ResetBits(X4_GPIO_PORT,X4_GPIO_PIN);
Y1=GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN);Y2=GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN);
Y3=GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN);Y4=GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN);
if(Y1==1&&Y2==0&&Y3==0&&Y4==0)
KeyVal=7;
if(Y1==0&&Y2==1&&Y3==0&&Y4==0)
KeyVal=8;
if(Y1==0&&Y2==0&&Y3==1&&Y4==0)
KeyVal=9;
if(Y1==0&&Y2==0&&Y3==0&&Y4==1)
KeyVal='C';
while(((GPIO_ReadInputDataBit(Y1_GPIO_PORT,Y1_GPIO_PIN))|(GPIO_ReadInputDataBit(Y2_GPIO_PORT,Y2_GPIO_PIN))|(GPIO_ReadInputDataBit(Y3_GPIO_PORT,Y3_GPIO_PIN))|(GPIO_ReadInputDataBit(Y4_GPIO_PORT,Y4_GPIO_PIN))) > 0);
return KeyVal;
}
/************************************
按键表盘为: 1 2 3 A
4 5 6 B
7 8 9 C
* 0 # D
************************************/
u8 ee,Data0=0;
void Key_Test(void)
{
int num;
char Freq[3]={'\0'};
num = Key_Scan();
if(ee!=num)
switch(num)
{
case 0: LCD_P6x8Str(1,1,"0"); break;
case 1: LCD_P6x8Str(1,1,"1"); break;
case 2: LCD_P6x8Str(1,1,"2"); break;
case 3: LCD_P6x8Str(1,1,"3"); break;
case 4: LCD_P6x8Str(1,1,"4"); break;
case 5: LCD_P6x8Str(1,1,"5"); break;
case 6: LCD_P6x8Str(1,1,"6"); break;
case 7: LCD_P6x8Str(1,1,"7"); break;
case 8: LCD_P6x8Str(1,1,"8"); break;
case 9: LCD_P6x8Str(1,1,"9"); break;//
case'A':LCD_P6x8Str(1,1,"A");
mode=0;LCD_P6x8Str(3,16," Sine Wave "); break;
case'B':LCD_P6x8Str(1,1,"B");
mode=1;LCD_P6x8Str(3,16," Square wave "); break;
case'C':LCD_P6x8Str(1,1,"C"); mode=2;LCD_P6x8Str(3,16,"Ttiangular wave"); break;
case 'D': LCD_P6x8Str(1,1,"D");
mode=3;LCD_P6x8Str(3,16," Sawtooth Wave ");
break;
case '#': LCD_P6x8Str(1,1,"#");
if(Data0>=5)Data0-=5;
Freq[0]=(20-Data0/5)/10+'0';
Freq[1]=(20-Data0/5)%10+'0';
LCD_P6x8Str(7,6*13,Freq);
break;
case '*': LCD_P6x8Str(1,1,"*");
if(Data0<50)Data0+=5; Freq[0]=(20-Data0/5)/10+'0';
Freq[1]=(20-Data0/5)%10+'0';
LCD_P6x8Str(7,6*13,Freq);
break;
}
ee=num;
}
//*************************************
12864
//*************************************
//*************************************
//写数据地址
//*************************************
void write_12864com(uint8_t com)
{
uint8_t temp = 0x01;
uint8_t k[8] = {0};
uint8_t i;
Check_status();
for(i=0; i<8; i++)
{
if(com & temp)
k[i] = 1;
else
k[i] = 0;
temp=temp << 1;
}
temp = 0x01;
RS=0;
RW=0;
D0=k[0];
D1=k[1];
D2=k[2];
D3=k[3];
D4=k[4];
D5=k[5];
D6=k[6];
D7=k[7];
En=1;
Delay_Ms(1);
En=0;
Delay_Ms(1);
}
//*************************************
//写数据
//*************************************
void write_12864data(uint8_t dat)
{
uint8_t temp = 0x01;
uint8_t k[8] = {0};
uint8_t i;
Check_status();//延时检查很重要,在连续刷新的时候
for(i=0; i<8; i++)
{
if(dat & temp)
k[i] = 1;
else
k[i] = 0;
temp=temp << 1;
}
temp = 0x01;
RS=1;
RW=0;
D0=k[0];
D1=k[1];
D2=k[2];
D3=k[3];
D4=k[4];
D5=k[5];
D6=k[6];
D7=k[7];
En=1;//在使能信号的下降沿写入了数据
Delay_Ms(1);
En=0;
Delay_Ms(1);
}
uint8_t read_12864data()
{
//这个函数的代码测试未通过
uint8_t temp = 0;
uint8_t k[8] = {0};
uint8_t i;
Check_status();
RS=1;
RW=1;
En=1;//使能端高电平读取数据
Delay_Ms(1);
LCD12864_DataRead_PortInit();//端口方向改为输入
k[0]=DI0;
k[1]=DI1;
k[2]=DI2;
k[3]=DI3;
k[4]=DI4;
k[5]=DI5;
k[6]=DI6;
k[7]=DI7;
LCD12864_DataWrite_PortInit();//端口方向改为输出
for (i=0; i<8; i++)
{
temp |= ((k[i]&0x01)<<i);
}
return temp;
}
void LCD_Set_Pos(uint8_t page,uint8_t x)//LCD当前光标设置,先选页,后选横向起始坐标
{
uint8_t Column=0;
(x>127)?(x=127):(x=x);
(x<1)?(x=0):(x=x);
if (page>8)
{
page=8;
}
else if (page<1)
{
page=1;
}
page=page-1;
if (x<=63)
{ LCDSelectScreen(LeftScreen);
LCDSetPage(page);
Column=x;
LCDSetColumn(Column);
}
else if (x>63)
{ LCDSelectScreen(RightScreen);
LCDSetPage(page);
Column = x-64;
LCDSetColumn(Column);
// if (Column == 0)
// { //如果第一次进入右半屏,需要空写一次,在设置一次,方可
// write_12864data(0x00);
// }
LCDSelectScreen(RightScreen);//上面的判断不对,应该是写两次命令就会解决右半屏首字下沉那个问题
LCDSetPage(page);
Column = x-64;
LCDSetColumn(Column);
}
}
void LCD_P6x8Str(uint8_t x, uint8_t y, char ch[])
{
uint8_t c = 0, i = 0, j = 0;
while (ch[j] != '\0')
{
c = ch[j]-32;
for (i = 0; i < 6; i++)
{ LCD_Set_Pos(x, y);//因为这个AMPIRE 分为左右半屏,所以光标设置必须写在这里,确保每个字符是完整的
write_12864data(F6x8[c][i]);
y = y+1;
}
j++;
}
}
void LCD_DispACat(unsigned char x, unsigned char y)
{
unsigned char i = 0, j = 0;
if (x > 4)
{
x = 4;
y++;
}
x=x-1;
for (j = 0; j < 4; j++)
{
LCD_Set_Pos(x, y);
x++;
for (i = 0; i < 32; i++)
write_12864data(LCD_cat[i + j * 32]);
}
}
//********************************************************
//12864初始化
//********************************************************
void Init_12864()
{
//端口初始化
RCC->APB2ENR|=1<<2; //使能PORTA时钟
GPIOA->CRH&=0X00000000;
GPIOA->CRH|=0X44333333;//PA8~13推挽输出,PA14,15浮空输入,因为这个型号的LCD要检测内部是否忙
GPIOA->ODR|=0X00FFFFFF;//PA8~13输出高
LCD12864_DataWrite_PortInit();
Reset=0;
Delay_Ms(1);
Reset=1;
Check_status();
LCDSelectScreen(AllScreen);//屏幕选择全屏
LCDSetOnOff(Off);
LCDSelectScreen(AllScreen);//屏幕选择全屏
LCDSetOnOff(On);
LCDSelectScreen(AllScreen);//屏幕选择全屏
LCDClearScreen(AllScreen);//清屏
LCDSetLine(0);
}
void LCD12864_DataWrite_PortInit()
{
RCC->APB2ENR|=1<<2;
GPIOA->CRL&=0X00000000; //PORTA低八位为数据位,推挽输出,写数据用
GPIOA->CRL|=0X33333333;
GPIOA->ODR|=0XFFFFFFFF;
}
void LCD12864_DataRead_PortInit()
{
RCC->APB2ENR|=1<<2;
GPIOA->CRL&=0X00000000; //PORTA低八位为数据位,浮空输入,读数据用
GPIOA->CRL|=0X44444444;
//GPIOA->ODR|=0XFFFFFFFF;
}
void LCDSelectScreen(ScreenTypedef screen)
{
switch(screen)
{
case AllScreen:
CS1=0;
CS2=0;
break;
case LeftScreen:
CS1=0;
CS2=1;
break;
case RightScreen:
CS1=1;
CS2=0;
break;
default :
break;
}
}
void LCDSetOnOff(LCDSetOnOffTypedef Status)
{
switch (Status)
{
case On:
write_12864com(0x3e+1);
break;
case Off:
write_12864com(0x3e);
break;
default:
break;
}
}
void LCDClearScreen(ScreenTypedef screen)
{
uint8_t i,j;
LCDSelectScreen(screen);
for (i=0; i<=7; i++)
{
LCDSetPage(i);
LCDSetColumn(0);
for(j=0; j<=63; j++)
{
write_12864data(0x00);//写完后地址自动加一
}
}
}
void LCDSetPage(uint8_t Page)
{
Page=0xb8|Page;
write_12864com(Page);
}
void LCDSetLine(uint8_t Line)
{
Line=0xc0|Line;
write_12864com(Line);
}
void LCDSetColumn(uint8_t Colnum)
{
Colnum=Colnum&0x3f;
Colnum=Colnum|0x40;
write_12864com(Colnum);
}
void Check_status()
{
D0=0;
D1=0;
D2=0;
D3=0;
D4=0;
D5=0;
D6=0;
D7=0;
RS=0;
RW=1;
//En=1;
// while(CheckPin == 0);//不启用忙检测
Delay_Ms(2);
//En=0;
}
7.小结
经过一段时间的努力,我的毕业设计终于接近尾声。在整个毕业设计过程中,非常感谢我的指导老师刘玉池老师。他平日里工作繁多,但在我做毕业设计的每个阶段,如选题、设计草案的确定、中期检查、后期详细设计及论文的编写等整个过程中,他都给予了我悉心的指导,并且为我提供实验室学习。刘老师对我的严格要求和谆谆教导是这次毕业设计能够顺利完成的重要保证。在课题研究期间,刘老师给我提出了很多指导性的建议,这使我受益匪浅。刘老师严谨的治学态度,广博的理论知识以及忘我的工作精神都给我留下了深刻的印象,并将积极地影响着我以后的学习和工作。在此谨向导师致以最诚挚的感谢!
同时,我也要感谢我的母校——天津工程师范学院。感谢母校五年来对我的大力栽培。在这五年中,我所学到的知识将使我受益终生。我仍会继续努力的,争取尽自己的最大努力回报母校的培育之情,使自己为社会创造出更多的价值!祝愿母校的将来更加美好!
最后,我还要感谢在此次毕业设计过程中给予我很大帮助的牟志喜和邢伟伟同学以及所有在此期间帮助过我的人,我衷心地祝福你们!
本部分对整个设计过程进行总结说明,对取得的成果进行简单评价,对存在的问题给出以后的展望或解决办法。
更多推荐
所有评论(0)