51单片机实验课程设计——简易低频信号发生器

实验目的

通过设计性实验,建立完善的单片机应用系统概念和工程应用思想,提高学生单片机应用系统的综合设计能力,增强学生的工程设计和实践力。把理论教学和单片机应用实践结合起来,让学生以项目设计方式接触当前单片机应用领域中较前沿的技术,通过查阅中外文资料,结合工程实际,为毕业设计和今后学生从事相关设计工作打下一个良好的基础,使其具有系统性的计算机、电子信息、物联网工程相关专业背景的基础理论和实践经验,达到培养应用型、复合型、创新型、国际化人才的宗旨


实验任务

  1. 采用串行接口的D/A转换器(TLC5615)
  2. 输出信号频率不作具体规定,尽可能将频率做高,但必须要有确定的频率值
  3. 可以显示幅值和频率,可切换波形:方波、锯齿波、三角波、正弦波,梯形波,一次输出一种波形
  4. 带有键盘显示模块,用于参数设置、输出状态显示等
  5. 输出信号类型、频率,幅值可键盘设置
  6. 除按键消抖外,所有定时必须采用定时器中断
  7. 采用示波器和IIC接口的OLED模块进行显示

系统原理

简易低频信号发生器大致可分为:TLC5615 ,OLED ,LCD1602,矩阵键盘四个部分。此部分将详细介绍其原理和功能

TLC5615

TLC5615是串行SPI接口的10位DAC,其引脚分布及功能如下

image-20250507224044981

image-20250507224852126

在进行数模转化的过程中主要需要用到三个引脚

No. NAME DESCRIPTION
1 DIN 串行数据输入端
2 SCLK 串行时钟输入端
7 OUT 模拟电压输出端

在考虑端口占用、硬件阻挡等一系列情况后(后文将不再赘述),本系统综合考虑将端口定义如下

sbit OLED_SDA = P2^0;
sbit OLED_SCL = P2^1;

对于波形输出,需要考虑两个参数——幅值和频率,首先我们分析输出幅值的影响因素

image-20250507230504340

可以看到输出电压与参考电压和输入数据这两个参数有关,参考电压在本系统选用幅值范围内固定为5V,那么我们只能通过改变输入数据来达到改变幅值的效果。本系统一共可调整为五种波形,这里将五种波形的波形输入数据按64采样点存储在五个数组中,关于采样点数量选取原因在频率部分将会进行阐述

这里定义了一个DA_Convert函数用于波形数据的输入

void DA_Convert(unsigned int dat)
{
    unsigned int i;
    dat <<= 6;  // 左移六位,使10位数据左对齐
    SCLK = 0;
    CS = 0;

    for(i = 0; i < 12; i++)
    {
        DIN = (bit)(dat & 0x8000);
        SCLK = 1;
        dat <<= 1;
        SCLK = 0;
    }
    SCLK = 0;
    CS = 1;
}

因为是10位数模转化器,所以先对其进行左对齐,将最左边的第一位赋值DIN,一位输入完毕后左移一位,循环12次输入完毕。循环次数与数据传入类型以及组成有关,12位数据由低2位填充位以及10位有效数据位组成,先传入10位有效数据位,再传入低2位任意传输位,数据将并行输入到10位DAC寄存器,经过一系统转化后就完成一次数模转化

image-20250507231928700

第二个需要考虑的是频率如何进行调整,思考一下不难想到波的频率与DA输出的频率有关,所以我们通过定时来对DA数据进行转化,详细过程在定时中断部分将会进行讲述


OLED

OLED模块看似困难,实则一点都不简单,但是再实例OLED.c文件的帮助下大大减少了调用难度,重点应该放在对基础代码的运用以及扩展,所以这里将结合代码对OLED进行介绍

在实例OLED.c文件中,需要用到几个关键代码

void OLED_Init(void)
{
    Delay(500);//初始化之前的延时很重要!
    OLED_WriteCommand(0xae);//--turn off oled panel
    OLED_WriteCommand(0x00);//---set low column address
    OLED_WriteCommand(0x10);//---set high column address
    OLED_WriteCommand(0x40);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    OLED_WriteCommand(0x81);//--set contrast control register
    OLED_WriteCommand(0xCF); // Set SEG Output Current Brightness
    OLED_WriteCommand(0xa1);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    OLED_WriteCommand(0xc8);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    OLED_WriteCommand(0xa6);//--set normal display
    OLED_WriteCommand(0xa8);//--set multiplex ratio(1 to 64)
    OLED_WriteCommand(0x3f);//--1/64 duty
    OLED_WriteCommand(0xd3);//-set display offset   Shift Mapping RAM Counter (0x00~0x3F)
    OLED_WriteCommand(0x00);//-not offset
    OLED_WriteCommand(0xd5);//--set display clock divide ratio/oscillator frequency
    OLED_WriteCommand(0x80);//--set divide ratio, Set Clock as 100 Frames/Sec
    OLED_WriteCommand(0xd9);//--set pre-charge period
    OLED_WriteCommand(0xf1);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    OLED_WriteCommand(0xda);//--set com pins hardware configuration
    OLED_WriteCommand(0x12);
    OLED_WriteCommand(0xdb);//--set vcomh
    OLED_WriteCommand(0x40);//Set VCOM Deselect Level
    OLED_WriteCommand(0x20);//-Set Page Addressing Mode (0x00/0x01/0x02)
    OLED_WriteCommand(0x02);//
    OLED_WriteCommand(0x8d);//--set Charge Pump enable/disable
    OLED_WriteCommand(0x14);//--set(0x10) disable
    OLED_WriteCommand(0xa4);// Disable Entire Display On (0xa4/0xa5)
    OLED_WriteCommand(0xa6);// Disable Inverse Display On (0xa6/a7) 
    OLED_WriteCommand(0xaf);//--turn on oled panel
    OLED_Clear(); //初始清屏
    OLED_SetCursor(0,0);
}

第一个是初始化函数,这个比较关键,OLED需要较多指令进行初始化,初始化函数已经整合了相关指令。这个函数需要注意的是进行初始化前的延时部分,延时是为了控制器和驱动芯片有足够的时间完成内部配置与状态切换,换句话说就是需要等待OLED硬件准备完毕才能开始写入命令,否则将会初始化失败

void OLED_SetCursor(unsigned char Y, unsigned char X)
{
    OLED_WriteCommand(0xB0 | Y);
    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));
    OLED_WriteCommand(0x00 | (X & 0x0F));
}

第二个是光标设置函数,这个函数用于在写入数据前进行位置的调整,这个地方需要注意的是OLED关于原点的选取以及坐标的划分OLED

Y 是以左上角为原点,向下方向的坐标,范围:0~7

X 是以左上角为原点,向右方向的坐标,范围:0~127

OLED的大小为64*128。OLED将纵坐标划分为八份,每一份称为一页,每一页有八位,就是所需的Page参数,横坐标按像素进行划分

image-20250508001129668

第三个是OLED_WriteData函数,函数本身的内容是已经定义好了的,主要是关于数据写入的端序问题。IIC与SPI不同,是高位在前,即在传输1个字节时,先传输高位(MSB),然后再传输低位(LSB)。例如传入 0000 0001后,其对应位置数据传输以及亮灭情况如下

1
0
0
0
0
0
0
0

为了更适应于常人的思维,通过这三个最重要的基础函数,可以定义出OLED_DrawPoint函数用于点亮指定坐标的像素,便于绘制波形

void OLED_DrawPoint(unsigned char X, unsigned char Y, unsigned char Mode)
{
    unsigned char Real_X = X;
    unsigned char Real_Y = 63-Y;

    unsigned char Page = Real_Y / 8;
    unsigned char BitValue = Real_Y % 8;

    OLED_SetCursor(Page, Real_X);
    if(Mode == 1)
        OLED_WriteData(1 << BitValue);
    else
        OLED_WriteData( ~(1 << BitValue));
}

连续的波在OLED显示等价于取采样点对连续的信号进行离散化,之前我们选取了64采样点,这里讲述一下原因。采样点数量如果过少,将会导致波形失真,无法呈现出较为真实的波形,在Sin函数中尤其明显;如果采样点过多,将会导致可设置的频率大大减小。综合考虑后选取64作为采样点数量

结合波形数据,可以定义出OLED_DrawWave()函数用于绘制波形,在定义的时候还需要解决一个问题。波形数据的范围为0~1023,但是OLED需要调整为0~63,所以需要先对数据进行一个缩放

void OLED_DrawWave()
{
        unsigned int i;
        unsigned int OLED_index=0;

        OLED_Clear();

        for(i=1;i<127;i++)
        {
                unsigned int currentIndex = OLED_index;
                unsigned int nextIndex = (OLED_index + (parameter[1]/5)) % 64;

                unsigned int currentdat=waveTable[waveType][currentIndex];
                unsigned int scaledValue = (currentdat * 63) / 1024;
                unsigned int currentout = (unsigned int)(((unsigned long)scaledValue * parameter[0]) >> 10);

                OLED_DrawPoint(i, currentout,1);
                OLED_index = nextIndex;
        }
}

与TLC5615不同的是,频率不能通过定时进行调整,而是通过间隔相应的点进行调整,达到不同频率的效果。

OLED所需引脚比较简单(数据线和时钟线),端口定义如下

sbit OLED_SDA = P2^0;
sbit OLED_SCL = P2^1;

LCD1602

LCD1602的原理也相对比较简单,常用的函数在实例代码中已经给出。本系统LCD1602用于状态显示,端口定义如下

sbit LCD_RS = P2^6;
sbit LCD_RW = P2^5;
sbit LCD_EN = P2^7;
#define LCD_DataPort P0 // 数据端口

此部分定义了Amp_Convert以及Fre_Convert用于将数据转化为字符串

 void Amp_Convert(void)
 {
     unsigned long scaled_value = (unsigned long)parameter[0] * 500 / 1023;
     unsigned int int_part = scaled_value / 100;
     unsigned int decimal_part = scaled_value % 100;

     Amp_display[0] = int_part + '0';
     Amp_display[1] = '.';
     Amp_display[2] = decimal_part / 10 + '0';
     Amp_display[3] = decimal_part % 10 + '0';
     Amp_display[4] = '\0';
 }
void Fre_Convert(void)
 {
     unsigned int temp = parameter[1];
     Fre_display[0] = (temp / 100) % 10 + '0';
     if(Fre_display[0] == '0')
        Fre_display[0] = 0x20;
     Fre_display[1] = (temp / 10) % 10 + '0';
     Fre_display[2] = temp % 10 + '0';
     Fre_display[3] = '\0';   
 }

矩阵键盘

矩阵键盘原理比较简单,在对矩阵键盘学习过程中已经讲述了多种方法。在本系统中矩阵键盘主要和定时中断搭配,起到调整系统参数和波形类型的作用

// 系统参数定义
#define Amplitude 750       // 初始幅值
#define Frequency 15        // 初始频率Hz
#define unit_amplitude 100   // 设置幅值时一次按键的加减值
#define unit_frequency 5    // 设置频率时一次按键的加减值
#define max_amplitude 950   // 控制幅值上限时的最大值(该值为DA转换前的数值,0~1023下对应0~5V)
#define max_frequency 20    // 控制频率上限时的最大值(单位Hz)
#define min_amplitude 550    // 控制幅值下限时的最小值(该值为DA转换前的数值,0~1023下对应0~5V)
#define min_frequency 5     // 控制频率下限时的最小值(单位Hz)

// 波形类型定义
#define SQUARE_WAVE      0   // 方波
#define SAWTOOTH_WAVE    1   // 锯齿波
#define TRIANGLE_WAVE    2   // 三角波
#define SIN_WAVE         3   // 正弦波
#define TRAPEZOIDAL_WAVE 4   // 梯形波

矩阵键盘以及设置模式进入按键选用默认端口

按下 P3^2 后进入设置模式,停止波形输出

sbit Set_Key = P3^2;   // 进入设置模式,停止波形输出

本系统我认为较为满意的是对于幅值和频率的设置以及与其他模块的联动,前文我们提到,波的频率与定时器设置初值有关,这里为了方便以及泛用性,定义了一个Timer0Init()函数用于对定时器初值设置

void Timer0Init(void)
{
    unsigned long sampling_rate = parameter[1] * 64; 
    unsigned long T_us = 1000000UL / sampling_rate; 

    unsigned long timer_ticks = (T_us * 92160) / 100000;

    if (timer_ticks > 65536UL)
        timer_ticks = 65536UL;

    timer_reload = 65536UL - timer_ticks;

    TMOD &= 0xF0;
    TMOD |= 0x01;
    TH0 = (timer_reload >> 8) & 0xFF;
    TL0 = timer_reload & 0xFF;
    EA = 1;
    ET0 = 1;
    TR0 = 1;
}

给中断也定义了一个Int0Init()

void Int0Init(void)
{
    Set_Key = 1;
    EA = 1;
    IT0 = 1;
    EX0 = 1;
}

定时函数内容,在定时函数内对DA转化器输入数据,达到频率可调的效果

void Timer0_Routine(void) interrupt 1
{
    unsigned int dat;
    unsigned int out;

    TH0 = (timer_reload >> 8) & 0xFF;
    TL0 = timer_reload & 0xFF;
    dat = waveTable[waveType][index];

    // 幅值缩放
    out = (unsigned int)(((unsigned long)dat * parameter[0]) >> 10);

    DA_Convert(out);

    index++;
    if(index >= 64)
    {
        index = 0;
    }
}

中断函数内容,调整调整系统参数和波形类型,在确认设置后同步更新OLED内容

void Int0_Key(void) interrupt 0
{
    EX0 = 0; // 防止按键抖动和设置过程中再次按下多次触发中断
    P2_0 = ~P2_0;
    while(1)
    {
        Delay(10);
        row_scan = 0x7F;
        while(1)
        {
            P1 = row_scan;
            if(P1 != row_scan)
            {
                Delay(10);
                if(P1 != row_scan)
                {
                    key_value = ~P1;
                    while(P1 != row_scan);
                    P2_0 = ~P2_0;
                    break;
                }
            }

            if(row_scan == 0x7F)      
                row_scan = 0xBF;      
            else if(row_scan == 0xBF) 
                row_scan = 0xDF;      
            else if(row_scan == 0xDF) 
                row_scan = 0xEF;      
            else if(row_scan == 0xEF) 
                row_scan = 0xF7;      
            else                      
                row_scan = 0x7F;      
        }

        switch(key_value) 
        {
            case 0x84:  //1行2列
                parameter_plus();
                break;
            case 0x44:  //2行2列
                parameter_minus();
                break;
            case 0x24:  //3行2列
                parameter_shift();
                break;
            case 0x28:  //3行1列
                waveType_right_shift();
                break;
            case 0x22:  //3行3列
                waveType_left_shift();
                break;
            case 0x11:  //4行4列
                OLED_DrawWave();
                Timer0Init(); 
                EX0 = 1;
                return;
        }
    }
}

系统模块图如下


程序框图

TLC5615程序框图

TLC5615程序框图2

OLED程序框图

OLED程序框图2

LCD1602程序框图

LCD1602程序框图2

矩阵键盘程序框图

矩阵键盘程序款图2

定时中断程序框图

定时中断程序框图3


电路原理图

电路原理图


实验结果

上电后,初始情况

image-20250508203349672

可切换波形

image-20250508203411187

image-20250508203430356

image-20250508203500530

image-20250508203523418

image-20250508203551985

可切换频率和幅值

image-20250508203639300

image-20250508203625149


程序清单

main.c

#include "Config.h"
#include "Delay.h"
#include "MatrixKey.h"
#include "LCD1602.h"
#include "INTimer0.h"
#include "OLED.h"

xdata unsigned int wave_display_buffer[64];

void main()
{
    LCD_Init();

    LCD_ShowString(1, 1, "Wave:");
    LCD_ShowString(1, 8, "Amp:");
    LCD_ShowString(1, 16, "V");
    LCD_ShowString(2, 8, "Fre:");
    LCD_ShowString(2, 15, "Hz");

    Amp_Convert();
    Fre_Convert();

    LCD_ShowString(2, 1, waveType_display[waveType]);
    LCD_ShowString(1, 12, Amp_display);
    LCD_ShowString(2, 12, Fre_display);

    Int0Init();
    Timer0Init(); 

    OLED_Init();
    OLED_DrawWave();

    while(1);
}

Config.h

#ifndef __CONFIG_H__
#define __CONFIG_H__

#include <REGX51.h>

// TLC5615 数模转换器
sbit DIN = P2^2;
sbit SCLK = P2^3;
sbit CS = P2^4;

// OLED显示屏(I2C接口)
sbit OLED_SDA = P2^0;
sbit OLED_SCL = P2^1;

// LCD1602显示屏
sbit LCD_RS = P2^6;
sbit LCD_RW = P2^5;
sbit LCD_EN = P2^7;
#define LCD_DataPort P0 // 数据端口

// 中断按键
sbit Set_Key = P3^2;   // 进入设置模式,停止波形输出

// 系统参数定义
#define Amplitude 750       // 初始幅值
#define Frequency 15        // 初始频率Hz
#define unit_amplitude 100   // 设置幅值时一次按键的加减值
#define unit_frequency 5    // 设置频率时一次按键的加减值
#define max_amplitude 950   // 控制幅值上限时的最大值(该值为DA转换前的数值,0~1023下对应0~5V)
#define max_frequency 20    // 控制频率上限时的最大值(单位Hz)
#define min_amplitude 550    // 控制幅值下限时的最小值(该值为DA转换前的数值,0~1023下对应0~5V)
#define min_frequency 5     // 控制频率下限时的最小值(单位Hz)

// 波形类型定义
#define SQUARE_WAVE      0   // 方波
#define SAWTOOTH_WAVE    1   // 锯齿波
#define TRIANGLE_WAVE    2   // 三角波
#define SIN_WAVE         3   // 正弦波
#define TRAPEZOIDAL_WAVE 4   // 梯形波

#endif

Delay.c

/**
 * @brief  提供指定毫秒的延时
 * @param  xms: 延时的毫秒数
 * @note   在11.0592MHz晶振下,延时准确
 * @retval 无
 */
void Delay(unsigned int xms)
{
    unsigned char i, j;

    for(i=xms;i>0;i--)
        for(j=124;j>0;j--);
}

/**
 * @brief  提供10微秒的延时
 * @param  无
 * @note   在11.0592MHz晶振下,延时准确
 * @retval 无
 */
void Delay_10us()
{
    unsigned char i;

    i = 2;
    while (--i);
}

Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);       // 毫秒延时函数
void Delay_10us();                  // 10微秒延时函数

#endif

INTimer0.c

#include "Config.h"
#include "Delay.h"
#include "TLC5615.h"
#include "MatrixKey.h"
#include "OLED.h"

unsigned char row_scan;
unsigned char key_value;
unsigned int timer_reload;

void Int0Init(void)
{
    Set_Key = 1;
    EA = 1;
    IT0 = 1;
    EX0 = 1;
}

void Timer0Init(void)
{
    unsigned long sampling_rate = parameter[1] * 64; 
    unsigned long T_us = 1000000UL / sampling_rate; 

    unsigned long timer_ticks = (T_us * 92160) / 100000;

    if (timer_ticks > 65536UL)
        timer_ticks = 65536UL;

    timer_reload = 65536UL - timer_ticks;

    TMOD &= 0xF0;
    TMOD |= 0x01;
    TH0 = (timer_reload >> 8) & 0xFF;
    TL0 = timer_reload & 0xFF;
    EA = 1;
    ET0 = 1;
    TR0 = 1;
}

void Int0_Key(void) interrupt 0
{
    EX0 = 0; // 防止按键抖动和设置过程中再次按下多次触发中断
    P2_0 = ~P2_0;
    while(1)
    {
        Delay(10);
        row_scan = 0x7F;
        while(1)
        {
            P1 = row_scan;
            if(P1 != row_scan)
            {
                Delay(10);
                if(P1 != row_scan)
                {
                    key_value = ~P1;
                    while(P1 != row_scan);
                    P2_0 = ~P2_0;
                    break;
                }
            }

            if(row_scan == 0x7F)      
                row_scan = 0xBF;      
            else if(row_scan == 0xBF) 
                row_scan = 0xDF;      
            else if(row_scan == 0xDF) 
                row_scan = 0xEF;      
            else if(row_scan == 0xEF) 
                row_scan = 0xF7;      
            else                      
                row_scan = 0x7F;      
        }

        switch(key_value) 
        {
            case 0x84:  //1行2列
                parameter_plus();
                break;
            case 0x44:  //2行2列
                parameter_minus();
                break;
            case 0x24:  //3行2列
                parameter_shift();
                break;
            case 0x28:  //3行1列
                waveType_right_shift();
                break;
            case 0x22:  //3行3列
                waveType_left_shift();
                break;
            case 0x11:  //4行4列
                OLED_DrawWave();
                Timer0Init(); 
                EX0 = 1;
                return;
        }
    }
}

void Timer0_Routine(void) interrupt 1
{
    unsigned int dat;
    unsigned int out;

    TH0 = (timer_reload >> 8) & 0xFF;
    TL0 = timer_reload & 0xFF;
    dat = waveTable[waveType][index];

    // 幅值缩放
    out = (unsigned int)(((unsigned long)dat * parameter[0]) >> 10);

    DA_Convert(out);

    index++;
    if(index >= 64)
    {
        index = 0;
    }
}

INTimer0.h

#ifndef __INTIMER0_H__
#define __INTIMER0_H__

void Int0Init(void);
void Timer0Init(void);

#endif

LCD1602.c

#include "Config.h"
#include "MatrixKey.h"
#include "Delay.h"

unsigned char Amp_display[5]; 
unsigned char Fre_display[4];

unsigned char code waveType_display[5][4] = {
    "Squ",  // 方波
    "Saw",  // 锯齿波
    "Tri",  // 三角波
    "Sin",  // 正弦波
    "Tra"   // 梯形波
};

/**
 * @brief  将幅度值转换为显示字符串
 * @param  无
 * @retval 无
 */
 void Amp_Convert(void)
 {
     unsigned long scaled_value = (unsigned long)parameter[0] * 500 / 1023;
     unsigned int int_part = scaled_value / 100;
     unsigned int decimal_part = scaled_value % 100;

     Amp_display[0] = int_part + '0';
     Amp_display[1] = '.';
     Amp_display[2] = decimal_part / 10 + '0';
     Amp_display[3] = decimal_part % 10 + '0';
     Amp_display[4] = '\0';
 }

 /**
  * @brief  将频率值转换为显示字符串
  * @param  无
  * @retval 无
  */
 void Fre_Convert(void)
 {
     unsigned int temp = parameter[1];
     Fre_display[0] = (temp / 100) % 10 + '0';
     if(Fre_display[0] == '0')
        Fre_display[0] = 0x20;
     Fre_display[1] = (temp / 10) % 10 + '0';
     Fre_display[2] = temp % 10 + '0';
     Fre_display[3] = '\0';   
 }

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
    LCD_RS=0;
    LCD_RW=0;
    LCD_DataPort=Command;
    LCD_EN=1;
    Delay(1);
    LCD_EN=0;
    Delay(1);
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
    LCD_RS=1;
    LCD_RW=0;
    LCD_DataPort=Data;
    LCD_EN=1;
    Delay(1);
    LCD_EN=0;
    Delay(1);
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
    if(Line==1)
    {
        LCD_WriteCommand(0x80|(Column-1));
    }
    else if(Line==2)
    {
        LCD_WriteCommand(0x80|(Column-1+0x40));
    }
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @note   添加延时,
  * @retval 无
  */
void LCD_Init(void)
{
    Delay(20);
    LCD_WriteCommand(0x38);
    LCD_WriteCommand(0x38);
    LCD_WriteCommand(0x38);
    LCD_WriteCommand(0x0c);
    LCD_WriteCommand(0x06);
    LCD_WriteCommand(0x01);
    Delay(2);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
    unsigned char i;
    LCD_SetCursor(Line,Column);
    for(i=0;String[i]!='\0';i++)
    {
        LCD_WriteData(String[i]);
    }
}

LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

extern unsigned char Amp_display[5];                // LCD1602显示幅值数据
extern unsigned char Fre_display[4];                // LCD1602显示频率数据
extern unsigned char code waveType_display[5][4];   // LCD1602显示波形数据

void Amp_Convert(void);
void Fre_Convert(void);

void LCD_Init(void);                                                                // 初始化
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);          // 显示字符串

#endif

MatrixKey.c

#include "Config.h"
#include "LCD1602.h"

unsigned int parameter[2] = {Amplitude, Frequency};            // 当前参数值
unsigned int code unit[2] = {unit_amplitude, unit_frequency};  // 增减单位
unsigned int code max[2] = {max_amplitude, max_frequency};     // 最大值
unsigned int code min[2] = {min_amplitude, min_frequency};     // 最小值

unsigned char waveType = SQUARE_WAVE;                           // 初始输出方波

// 模式切换按键
bit switch_value = 0;                                           // 当前设置模式标志位,切换幅值频率

/**
 * @brief  参数增加函数
 * @param  无
 * @retval 无
 */
void parameter_plus(void)
{
    parameter[switch_value] = parameter[switch_value] + unit[switch_value];
    if(parameter[switch_value] > max[switch_value])
    { 
        parameter[switch_value] = min[switch_value];
    }
    Amp_Convert();
    Fre_Convert();
    LCD_ShowString(1, 12, Amp_display);
    LCD_ShowString(2, 12, Fre_display);
}

/**
 * @brief  参数减少函数
 * @param  无
 * @retval 无
 */
void parameter_minus(void)
{
    parameter[switch_value] = parameter[switch_value] - unit[switch_value];
    if(parameter[switch_value] < min[switch_value])
    {
        parameter[switch_value] = max[switch_value];
    }
    Amp_Convert();
    Fre_Convert();
    LCD_ShowString(1, 12, Amp_display);
    LCD_ShowString(2, 12, Fre_display);
}

/**
 * @brief  切换参数设置对象(幅值/频率)
 * @param  无
 * @retval 无
 */
void parameter_shift(void)
{
    switch_value = !switch_value;
}

/**
 * @brief  波形类型向右切换
 * @param  无
 * @retval 无
 */
void waveType_right_shift(void)
{
    waveType++;
    if(waveType == 5)
    {
        waveType = 0;
    }
    LCD_ShowString(2, 1, waveType_display[waveType]);
}

/**
 * @brief  波形类型向左切换
 * @param  无
 * @retval 无
 */
void waveType_left_shift(void)
{
    waveType--;
    if(waveType == 255)  // 无符号数下溢
    {
        waveType = 4;
    }
    LCD_ShowString(2, 1, waveType_display[waveType]);
}

MatrixKey.h

#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__

extern unsigned int parameter[2];           // 参数类型
extern unsigned int code unit[2];           // 参数增减单位
extern unsigned int code max[2];            // 参数最小值
extern unsigned int code min[2];            // 参数最小值
extern unsigned char waveType;              // 初始输出方波

void parameter_plus(void);                  // 参数增加函数
void parameter_minus(void);                 // 参数增加函数
void parameter_shift(void);                 // 切换参数设置对象(幅值/频率)
void waveType_right_shift(void);            // 波形类型向右切换
void waveType_left_shift(void);             // 波形类型向左切换

#endif

OLED.c

#include "Config.h"
#include "Delay.h"
#include "MatrixKey.h"
#include "TLC5615.h"

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void OLED_I2C_Start(void)
{
    OLED_SDA = 1;
    OLED_SCL = 1;
    OLED_SDA = 0;
    OLED_SCL = 0;
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void OLED_I2C_Stop(void)
{
    OLED_SDA = 0;
    OLED_SCL = 1;
    OLED_SDA = 1;
}

/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的一个字节
  * @retval 无
  */
void OLED_I2C_SendByte(unsigned char Byte)
{
    unsigned char i;
    for (i = 0; i < 8; i++)
    {
        if(Byte & 0x80)
        {
            OLED_SDA = 1;
        }
        else
        {
            OLED_SDA = 0;
        }
        OLED_SCL = 1;
        OLED_SCL = 0;
        Byte <<= 1;
    }
    OLED_SDA = 1;
    OLED_SCL = 1;
    OLED_SCL = 0;
}

/**
  * @brief  OLED写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void OLED_WriteCommand(unsigned char Command)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);
    OLED_I2C_SendByte(0x00);
    OLED_I2C_SendByte(Command); 
    OLED_I2C_Stop();
}

/**
  * @brief  OLED写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void OLED_WriteData(unsigned char Data)
{
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);
    OLED_I2C_SendByte(0x40);
    OLED_I2C_SendByte(Data);
    OLED_I2C_Stop();
}

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(unsigned char Y, unsigned char X)
{
    OLED_WriteCommand(0xB0 | Y);
    OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));
    OLED_WriteCommand(0x00 | (X & 0x0F));
}

/**
  * @brief  设置OLED单个像素
  * @param  X 像素点的X坐标,范围:0~127,从左下角开始计算
  * @param  Y 像素点的Y坐标,范围:0~63,从左下角开始计算
  * @param  Mode 模式。1点亮,0熄灭
  * @retval 无
  */
void OLED_DrawPoint(unsigned char X, unsigned char Y, unsigned char Mode)
{
    unsigned char Real_X = X;
    unsigned char Real_Y = 63-Y;

    unsigned char Page = Real_Y / 8;
    unsigned char BitValue = Real_Y % 8;

    OLED_SetCursor(Page, Real_X);
    if(Mode == 1)  // 点亮
        OLED_WriteData(1 << BitValue);
    else           // 熄灭
        OLED_WriteData( ~(1 << BitValue));
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
    unsigned char i, j;
    for(i = 0; i < 8; i++)
    {
        OLED_SetCursor(i,0);
        for(j = 0; j < 128; j++)
        {
            OLED_WriteData(0);
        }
    }
}

/**
  * @brief  OLED次方函数
  * @retval 返回值等于X的Y次方
  */
unsigned long OLED_Pow(unsigned long X, unsigned long Y)
{
    unsigned long Result = 1;
    while (Y--)
    {
        Result *= X;
    }
    return Result;
}

/**
  * @brief  OLED初始化
  * @param  无
  * @retval 无
  */
void OLED_Init(void)
{
    Delay(500);//初始化之前的延时很重要!
    OLED_WriteCommand(0xae);//--turn off oled panel
    OLED_WriteCommand(0x00);//---set low column address
    OLED_WriteCommand(0x10);//---set high column address
    OLED_WriteCommand(0x40);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
    OLED_WriteCommand(0x81);//--set contrast control register
    OLED_WriteCommand(0xCF); // Set SEG Output Current Brightness
    OLED_WriteCommand(0xa1);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
    OLED_WriteCommand(0xc8);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
    OLED_WriteCommand(0xa6);//--set normal display
    OLED_WriteCommand(0xa8);//--set multiplex ratio(1 to 64)
    OLED_WriteCommand(0x3f);//--1/64 duty
    OLED_WriteCommand(0xd3);//-set display offset   Shift Mapping RAM Counter (0x00~0x3F)
    OLED_WriteCommand(0x00);//-not offset
    OLED_WriteCommand(0xd5);//--set display clock divide ratio/oscillator frequency
    OLED_WriteCommand(0x80);//--set divide ratio, Set Clock as 100 Frames/Sec
    OLED_WriteCommand(0xd9);//--set pre-charge period
    OLED_WriteCommand(0xf1);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
    OLED_WriteCommand(0xda);//--set com pins hardware configuration
    OLED_WriteCommand(0x12);
    OLED_WriteCommand(0xdb);//--set vcomh
    OLED_WriteCommand(0x40);//Set VCOM Deselect Level
    OLED_WriteCommand(0x20);//-Set Page Addressing Mode (0x00/0x01/0x02)
    OLED_WriteCommand(0x02);//
    OLED_WriteCommand(0x8d);//--set Charge Pump enable/disable
    OLED_WriteCommand(0x14);//--set(0x10) disable
    OLED_WriteCommand(0xa4);// Disable Entire Display On (0xa4/0xa5)
    OLED_WriteCommand(0xa6);// Disable Inverse Display On (0xa6/a7) 
    OLED_WriteCommand(0xaf);//--turn on oled panel
    OLED_Clear(); //初始清屏
    OLED_SetCursor(0,0);
}

/**
  * @brief  OLED根据LCD参数绘制波形
  * @param  无
  * @retval 无
  */
void OLED_DrawWave()
{
        unsigned int i;
        unsigned int OLED_index=0;

        OLED_Clear();

        for(i=1;i<127;i++)
        {
                unsigned int currentIndex = OLED_index;
                unsigned int nextIndex = (OLED_index + (parameter[1]/5)) % 64;

                unsigned int currentdat=waveTable[waveType][currentIndex];
                unsigned int scaledValue = (currentdat * 63) / 1024;
                unsigned int currentout = (unsigned int)(((unsigned long)scaledValue * parameter[0]) >> 10);

                OLED_DrawPoint(i, currentout,1);
                OLED_index = nextIndex;
        }
}

OLED.h

#ifndef __OLED_H__
#define __OLED_H__

void OLED_Init();                                                           // OLED初始化
void OLED_Clear();                                                          // OLED清屏
void OLED_DrawPoint(unsigned char X, unsigned char Y, unsigned char Mode);  // 设置OLED单个像素
void OLED_DrawWave();                                                       // OLED根据LCD参数绘制波形

#endif

TLC5615.c

#include "Config.h"

// 方波取样表
unsigned int code Square_list[64] = {
    1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
    1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

// 锯齿波取样表
unsigned int code Sawtooth_list[64] = {
    0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240,
    256, 272, 288, 304, 320, 336, 352, 368, 384, 400, 416, 432, 448, 464, 480, 496,
    512, 528, 544, 560, 576, 592, 608, 624, 640, 656, 672, 688, 704, 720, 736, 752,
    768, 784, 800, 816, 832, 848, 864, 880, 896, 912, 928, 944, 960, 976, 992, 1008
};

// 三角波取样表
unsigned int code Triangle_list[64] = {
    0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480,
    512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992,
    1023, 992, 960, 928, 896, 864, 832, 800, 768, 736, 704, 672, 640, 608, 576, 544,
    512, 480, 448, 416, 384, 352, 320, 288, 256, 224, 192, 160, 128, 96, 64, 32
};

// 正弦波取样表
unsigned int code Sin_list[64] = {
    544, 585, 637, 673, 709, 758, 803, 843, 879, 911, 943, 968, 988, 1004, 1016, 1024,
    1024, 1020, 1012, 996, 980, 956, 927, 895, 859, 823, 778, 734, 685, 637,
    589, 536, 488, 435, 387, 339, 290, 246, 202, 165, 129, 97, 69, 44, 28, 12,
    4, 0, 0, 8, 20, 36, 56, 81, 113, 145, 181, 222, 266, 315, 363, 411, 460, 516
};

// 梯形波取样表
unsigned int code Trapezoidal_list[64] = {
    0, 64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960,
    1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
    1023, 960, 896, 832, 768, 704, 640, 576, 512, 448, 384, 320, 256, 192, 128, 64,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

// 取样索引
unsigned int index = 0;
unsigned int *waveTable[] = {Square_list, Sawtooth_list, Triangle_list, Sin_list, Trapezoidal_list};

/**
 * @brief  DA数模转换
 * @param  dat: 要转换的数据(10位,unsigned int类型)
 * @retval 无
 */
void DA_Convert(unsigned int dat)
{
    unsigned int i;
    dat <<= 6;  // 左移六位,使10位数据左对齐
    SCLK = 0;
    CS = 0;

    for(i = 0; i < 12; i++)
    {
        DIN = (bit)(dat & 0x8000);
        SCLK = 1;
        dat <<= 1;
        SCLK = 0;
    }
    SCLK = 0;
    CS = 1;
}

TLC5615.h

#ifndef __TCL5615_H__
#define __TCL5615_H__

extern unsigned int code Square_list[64];        // 方波取样表
extern unsigned int code Sawtooth_list[64];      // 锯齿波取样表
extern unsigned int code Triangle_list[64];      // 三角波取样表
extern unsigned int code Sin_list[64];           // 正弦波取样表
extern unsigned int code Trapezoidal_list[64];   // 梯形波取样表

extern unsigned int index;
extern unsigned int *waveTable[];

void DA_Convert(unsigned int dat);  // DA数模转换

#endif

评论

  1. 博主 置顶
    3 周前
    2025-5-08 22:14:18

    通过网盘分享的文件:51单片机实验课程设计——简易低频信号发生器.zip
    链接: https://pan.baidu.com/s/184P2YmG2RWtwf79KucEnBw?pwd=xcrf 提取码: xcrf
    –来自百度网盘超级会员v5的分享

    来自重庆

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇