占空比可调的控制系统设计与实现

摘要:本文介绍一种占空比可调的控制系统设计与实现。该系统以单片机为核心控制单元,通过对外围电路的控制来实现对输出波形的占空比的精确调节,并能对运行信号参数进行实时显示,通过观察流水灯的切换速度直观感受占空比的改变。

占空比是脉冲宽度调制波的一个重要参数。脉冲宽度调制波通常由一列占空比不同的矩形脉冲构成,其占空比与信号的瞬时采样值成比例。脉冲宽度调制(PWM)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,脉宽调制(PWM)信号广泛应用于电机控制、开关电源设计等诸多场合。PWM 信号在自动控制中系统中起着重要的作用,其控制作用受外界干扰小,使得系统工作精确可靠,研究 PWM 信号的产生和原理具有重要的意义。

PWM 控制技术以其控制简单、灵活和动态响应好的优点而成为电力电子技术最广泛应用的控制方式,也是人们研究的热点。由于当今科学技术的发展已经没有了学科之间的界限,结合现代控制理论思想或实现无谐振软开关技术将会成为PWM 控制技术发展的主要方向之一。

关键字:脉宽调制;STC89C52;占空比;LCD1602;矩阵键盘;流水灯

一、引言

​ 在当今的数字化世界中,微控制器在各种应用中发挥着至关重要的作用,从简单的家用电器到复杂的工业控制系统,都离不开微控制器的控制和管理。51单片机,作为一种广泛使用的微控制器,以其强大的功能和灵活的编程性能,成为了许多设计者的首选。本课程设计的主题是“基于51单片机占空比可调的控制系统设计与实现”,这是一个具有实际应用价值和理论意义的课题。

​ 占空比是脉冲宽度调制(PWM)中的一个重要参数,它决定了输出信号的平均电平。通过调整占空比,我们可以精确地控制电机的速度、LED的亮度、电源的输出电压等。在本设计中,我们将实现一个占空比连续可调的控制系统,并通过LCD显示占空比的数值,通过跑马灯LED的切换速度的快慢显示占空比大小。

​ 这个设计的意义在于,它不仅可以帮助我们理解和掌握51单片机的基本操作和编程技术,还可以让我们了解PWM的工作原理和应用。此外,通过实现这个设计,我们可以学习如何设计和实现一个实际的控制系统,这对于我们未来的学习和工作都是非常有帮助的。

​ 在实际应用中,这种基于51单片机的占空比可调的控制系统可以广泛应用于各种电子设备和系统中,例如电机控制、电源管理、LED照明等。通过调整占空比,我们可以实现对这些设备和系统的精确控制,从而提高其性能和效率。

​ 总的来说,这个课程设计不仅具有理论意义,也具有实际应用价值。我们期待通过这个设计,能够提高我们的技术能力,为我们的未来学习和工作打下坚实的基础。

二、设计内容及要求

2.1 设计内容

​ 该系统以单片机为核心控制单元,并利用Proteus实现功能仿真,仿真后的程序下载到51单片机,通过对外围电路的控制来实现对输出波形的占空比的精确调节,并能对运行信号参数进行实时显示,通过观察流水灯的切换速度直观感受占空比的改变。

2.1 设计要求

​ 本次课程设计主要有以下9个设计要求:

  1. 根据功能要求选择设计方案,并进行论证;

  2. 完成系统整体设计方案,画出电路的总体方框图,并在Proteus上设计出原理图;

  3. 绘制程序说明及流程图并完成程序设计;

  4. 实现占空比连续可调;

  5. 通过LCD或者点阵式LED显示占空比的数值;

  6. 通过跑马灯LED灯的切换速度的快慢显示占空比大小;

  7. 在Proteus对电路及程序进行仿真调试,直到正确显示所要求信息的直观显示效果;

  8. 在电路开发板或实验箱上实现课程设计题目的功能要求;

  9. 写出经验体会和总结,撰写课程设计报告。

三、设计原理

3.1 复位电路原理

复位电路的作用是使单片机初始化,即通过复位把单片机内部的各个部分恢复到预先已知的特定状态,使之成为编制程序、执行程序和调试程序的起点。首先我们对单片机的RST引脚进行了解。
RST:复位输入。晶振工作时,RST 脚将持续 2 个机器周期高电平将使单片机复位。看门狗计时完成后,RST 脚输出96 个晶振周期的高电平。特殊寄存器AUXR(地址8EH)上的 DISRTO 位可以使此功能无效。DISRTO 默认状态下,复位高电平有效,如图3-1示。

image-20230627233127349

3.2 时钟电路原理

时钟电路主要是用来为系统提供时钟信号,是单片机系统中不可缺少的一部分,8XX51 系列单片机的时钟信号通常用两种电路形式得到:内部振荡方式和外部振荡方式。在该系统中将才用内部振荡方式。
XTAL1:振荡器反相放大器和内部时钟发生电路的输入端。
XTAL2:振荡器反相放大器的输出端。

3.3 按键电路原理

1.人机交互接口的设计

​ 所谓人机交互接口,是指人与计算机之间建立联系、交互信息的输入/输出设备的接口。他们是计算机应用系统中必不可少的输入、输出设备,是控制系统与操作人员之间的交互窗口。一个安全可靠的控制系统必须具有方便的交互功能。操作人员可以通过系统显示的内容,及时掌握生产情况,并可通过键盘输入数据,传递命令,对计算机应用系统进行人工干扰,使其随时能按照操作人员的意图工作。

2.键盘设计需要解决的几个问题

​ 键盘是若干按键的集合,是向系统提供操作人员干预命令及数据的接口设备。键盘可分为编码键盘和非编码键盘两种类型。编码键盘能自动识别按下的键并产生相应代码,以并行或串行方式发给 CPU。它使用方便,接口简单,响应速度快,但需要专用的硬件电路。非编码键盘则是通过软件来确定按键并计算键值。这种方法虽然没有编码键盘速度快,但它不需要专用的硬件支持,因此得到了广泛的应用。键盘是计算机应用系统中的一个重要组成部分。

image-20230627233334840

3.4 流水灯电路原理

​ LED,即发光二极管,它的种类很多,参数也不尽相同。二极管通常的正向导通电压是1.8V到2.2V 之间,工作电流一般在1mA~20mA 之间。其中,当电流在 1mA~5mA 之间变化时,随着通过 LED 的电流越来越大,我们的肉眼会明显感觉到这个小灯越来越亮,而当电流从5mA~20mA之间变化时,发光二极管的亮度变化不是太明显。当电流超过20mA时,LED会有烧坏的危险,电流越大,烧坏的也越快,电路如图3-3示。

image-20230627233612172

四、设计方案

​ 本设计采用 STC89C52及其外围扩展系统,软件方面主要是应用 C 语言设计程序。系统以 STC89C52 单片机为核心,配置相应的外设及接口电路,用 C 语言开发,组成一个 PWM 信号占空比连续可调系统。该硬件电路设计具有典型性。同时,本系统中任何一部分电路模块均可移植于实用开发系统的设计中,电路设计具有实用性。

4.1 方案论证与比较

​ 基于单片机的 PWM 信号占空比连续可调系统是一个实际应用系统,可为相关实验及实际应用提供支持。本设计包括硬件系统的设计及 C 语言在基本控制中的应用。根据设计要求系统要满足两个条件:一是能够产生频率连续可调整PWM 信号,二是用数码管或 LCD 显示信号频率值和占空比。下面提出几种方案进行论证比较。

波形发生部分:

方案一:使用锁相环通过分频运算实现频率的步进,这种方案频率稳定度高,但程控比较困难,而且步进范围过大,鉴于锁相环技术比较复杂,没有采用这种方案。
方案二:使用单片机的定时器设置定时时间,每半个周期对I/O 口取反一次,从而实现频率输出。这种方案虽然在高频频段误差比较大,但是编程简单控制容易,权衡以上利弊,我们选择了方案二。

键盘与显示控制部分:
方案一:使用并行控制器8279或IIC 总线控制器ZLG7290 构建键盘与显示部分,编程简单功能强大,但成本较高而且接口协议比较复杂,我们没有采用。
方案二:使用单片机本身的I/O 口做键盘和显示控制,能够节省大量外围器件,符合硬件软化的原则,而且本系统对按键和显示部分的要求并不高,所以我们采用了这种方案。

​ 综上所述,通过分析系统要求,我们可以确定实现方案二为最佳方案,采用单片机及其外围扩展系统,软件方面主要采用C语言设计程序,系统以单片机单片机为核心,配置相应的外设及接口电路,用C语言开发,组成一个PWM信号占空比连续可调系统。该系统由硬件和软件两大部分组成:

硬件部分
(1)51单片机;
(2)LCD1602屏显示;
(3)4×4 的16位矩阵键盘;
(4)时钟电路与复位电路;
(5)具有波形产生的功能;

软件部分
(1)系统复位初始化;
(2)键盘扫描与处理;
(3)按键服务程序;
(4)定时器中断服务程序;
(5)PWM 波占空比连续可调程序;

4.2 系统可行性分析及总体方案设计

  1. 元器件的选择及其可行性讨论

​ 根据技术指标及系统设计目的,经研究芯片的选择如下:
​ ① 主控芯片采用宏晶科技公司的STC89C52;
​ ② 采用12MHz 的晶振器为STC89C52提供时钟信号;
​ ④ 占空比显示器采用1602LCD屏;
​ ⑤ 普中开发板自带LED小灯电路组成流水灯;
大部分的芯片及器件都可以通过网络购买,所以器件的选择完全可行。

  1. 经济上的可行性讨论

​ 本设计是一个实验系统,芯片的选择在前面已经讨论,从前面的讨论中可见芯片大部分可在网上找到。因此,设计费用主要集中在购买元器件上,而大部分的元器件又不是很贵,所以经济上本设计完全可行。

  1. 总体方案设计

​ 一个单片机主系统的硬件电路设计包含两部分内容:一是单片机系统扩展部分设计,它包括存储器扩展和接口扩展。存储器扩展指 EPROM、EEPROM 和RAM 的扩展。接口扩展是指各接口芯片以及其他功能器件的扩展。二是各功能模块的设计,如信号检测功能模块、信号控制功能模块、人机对话功能模块、通讯功能模块等,根据系统功能要求配置相应的键盘、显示器等外围设备。主系统框图如图3-1所示。

image-20230627005255515

4.3 系统硬件设计(方法、思路、步骤)

一、 复位与时钟电路设计

1.复位电路设计
​ 单片机的复位是靠外电路实现的,在时钟电路工作后,只要在单片机的 RST 引脚上出现 24 个时钟振荡脉冲(2 个机器周期)以上的高电平,单片机便实现初始化状态复位。为了保证应用系统可靠地复位,通常是RST 引脚保持 10ms 以上的高电平。复位电路连接如图3-2所示。此电路仅用一个电容及一个电阻。系统上电时,在 RC 电路充电过程中,由于电容两端电压不能跳变,故使 RESET 端电平呈高电位,系统复位。经过一段时间,电容充电,使 RESET 端呈低电位,复位结束。

image-20230627010426001

  1. 时钟电路设计
    ​ 在引脚 XTAL1 和 XTAL2 外接晶体振荡器(简称晶振),就构成了内部振荡方式。由于单片机内部有一个高增益反向放大器,当外接晶振后,就构成了自激振荡器,并产生振荡时钟脉冲。晶振通常选用 6MHZ、12MHZ 或24MHZ。内部振荡方式如图4-3所示。图中电容 C1、C2 起稳定振荡频率、快速起振的作用。电容值一般为 5~30pF(这里取22pF)。内部振荡方式所得时钟信号比较稳定,实用电路中使用较多。

image-20230627011014066

​ 外部振荡方式是把已有的时钟信号引入单片机内。这种方式适宜用来使单片机的时钟与外部信号保持一致。外部振荡方式电路如图4-4所示。

image-20230627011126804

二、按键电路设计

1.系统键盘的实现

​ 键盘的结构形式一般有两种:独立式键盘与矩阵式键盘。独立式键盘就是各按键相互独立,每个按键各接一根 I/O 口线,每根 I/O 口线上的按键都不会影响其它的 I/O 口线,示例如图 4-5 所示。矩阵式键盘又叫行列式键盘。用I/O 口线组成行、列结构,键位设置在行列的交点上。例如 4×4 的行、列结构可组成 16 个键的键盘,比一个键位用一根 I/O 口线的独立式键盘少了一半的 I/O 口线。对矩阵键盘的工作过程可分两步:第一步是 CPU 首先检测键盘上是否有键按下;第二步是再识别是哪一个键按下。

image-20230627082719432

依据上述工作原理,结合本设计实际,设计出本系统键盘结构如图4-6示

image-20230627083022588

工作原理如下:

  1. 检测键盘上是否有键按下。检测的方法是输出全“0”信号到所有的列线(P1.0~P1.3)
    上,然后读取行线(P1.4~P1.7)的状态,若所有行线全为“1”,则无键闭合,否则
    有键闭合。
  2. 去除键抖动。当检测到有键按下时,延时一段时间后再做下一步的检测判断。
  3. 若有键被按下,应识别时哪一个键闭合。方法是对键盘进行扫描,就是依次给每一
    条列线送出 0 信号,其余各列线均为1,并相继检测每一次扫描时所对应的行状态。
    在每组行输出时读取各行,若全为“1”,则表示为“0”这一行没有键闭合。由此的到
    闭合键的航值和列值,然后可采用计算法或查表法将闭合键的行值和列值转换成所
    定义的键值。
  4. 判断闭合键是否释放,若没释放急需等待。
  5. 用计算法得到键值A,转向相应的处理程序。
  6. K1、K2、K3、K4、K5、K6、K7、K8、K9、K10分别对应键值0~9用来控制占空比的大小并连续可调,K12实现指示调节以及调节完成。

三、显示器电路设计

1.LCD1602显示器的结构与原理

​ LCD1602液晶显示屏是一种字符型液晶显示模块,可以显示ASCll码的标准字符和其他一些内置的特殊字符,还可以内置8个自定义字符。从它的名字我们就可以看出它的显示容量,就是可以显示 2 行,每行 16 个字符的液晶。它的工作电压是 4.5V~5.5V,对于这点我们设计电路的时候,直接按照 5V 系统设计,但是保证我们的 5V 系统最低不能低于 4.5V。在 5V 工作电压下测量它的工作电流是 2mA。其主要技术参数表格如图4-7示

image-20230627085253030

​ 1602 液晶一共 16 个引脚,每个引脚的功能的功能介绍如图4-8示。

image-20230627090121620

​ 液晶的电源 1 脚 2 脚以及背光电源 15 脚 16 脚,正常接即可。

​ 3 脚叫做液晶显示偏压信号,当我们要显示一个字符的时候,有的黑点显示,有的黑点就不能显示,这样就可以实现我们想要的字符了。3脚用来调整显示的黑点和不显示的之间的对比度,可以让我们的显示更加清晰。在进行电路设计实验的时候,通常的办法是在该引脚上接个电位器,即滑动变阻器。通过调整电位器的分压值,调整3脚的电压。

​ 4 脚是数据命令选择端。对于液晶,有时要发送一些命令,实现我们想要的一些状态,有时要发给它一些数据,让它显示出来,液晶通过这个引脚来判断接收到的是命令还是数据,该引脚接与P2.6连接在一起。数据/命令选择端(H/L),当该引脚是 H(High)高电平时,是数据,该引脚是 L(Low)低电平时,是命令。

​ 5 脚的功能是读写选择端。控制该引脚既可以写给液晶数据或者命令,也可以读取液晶内部的数据或状态。因为液晶本身内部有 RAM,实际上发送给液晶的命令或者数据,液晶需要先保存在缓存中,然后再写如内部的寄存器或者 RAM中。故在进行读写操作前,首先要读取液晶当前状态,是不是在“忙”,如果不忙,我们可以读写数据,如果在“忙”,需要等待液晶,再进行操作。读状态是常用的。该引脚与P2.5连接在一起。

​ 6 脚是使能信号,控制液晶正常读写命令和数据。该引脚与P2.7连接在一起。

​ 7 到 14 引脚就是 8 个数据引脚了,通过这8个引脚读写数据和命令的。统一接到了 P0 口上。系统LCD1602液晶显示框图如图4-9示。

image-20230627092626520

四、流水灯电路设计

​ LED,即发光二极管,它的种类很多,参数也不尽相同。二极管通常的正向导通电压是1.8V到2.2V 之间,工作电流一般在1mA~20mA 之间。其中,当电流在 1mA~5mA 之间变化时,随着通过 LED 的电流越来越大,我们的肉眼会明显感觉到这个小灯越来越亮,而当电流从5mA~20mA之间变化时,发光二极管的亮度变化不是太明显。当电流超过20mA时,LED会有烧坏的危险,电流越大,烧坏的也越快。系统使用的流水灯电路如图4-10示。

image-20230627093646229

​ 在系统中设计通过控制流水灯的切换速度来反映占空比的大小。流水灯的切换速度越快,高电平持续时间短,代表占空比越小;流水灯的切换速度越慢,高电平持续时间长,代表占空比越小。

4.4 系统软件设计(方法、思路、步骤)

一、软件总体设计

​ 应用系统中的应用软件是根据系统功能要求而设计的,能可靠地实现系统的各种功能。一个优秀的应用系统的应具有下列特点:
​ (1) 根据软件功能要求,将系统软件分成若干个独立的部分。设计出软件的总体结构,使其结构清晰、流程合理。
​ (2) 要树立结构化程序设计风格,各功能程序模块化、子程序化。既便于调试、链接,又便于移植、修改。
​ (3) 建立正确的数学模型。即根据功能要求,描述各个输入和输出变量之间的数学关系,它是关系到系统好坏的重要因素。
​ (4) 为提高软件设计的总体效率,以简明、直观法对任务进行描述,在编写应用软件之前,应绘制出程序流程图。
​ (5) 要合理分配系统资源,包括ROM、RAM、定时数器、中断资源等。
​ (6) 注意在程序的有关位置处写上功能注释,提高程序的可读性。
​ (7) 加强软件抗干扰设计,它是提高系统应用可靠性的有利措施。

image-20230627100740970

​ 本系统的软件包括以下几个程序模块:

​ (1) 初始化程序;
​ (2) 液晶显示程序;
​ (3) 键盘扫描程序与处理程序;
​ (4) 定时器0 服务程序;
​ (5) PWM 波发生程序及其服务程序;
​ (6) 流水灯控制程序;

系统主程序流程图如图4-11示。

二、系统初始化程序设计

复位程序完成以下工作

  1. LCE1602液晶屏显示初始设置的占空比50%以及提示字符初始界面;
  2. 初始频率设置为 100HZ;
  3. 将频率值转换成定时器的初值;
  4. 置定时器 0 工作于方式 1,即16 位定时器方式,送入定时器0 定时初值,启动定时器 0 工作;
  5. 初始化转键盘扫描程序。
  6. 依据设置的初始占空比大小,初始化LED流水灯电路

系统的初始化流程如图 4-12示。

image-20230627105306288

三、键盘扫描及处理程序设计

这部分程序包括如下几部分:

  1. 键盘扫描程序;

  2. 先对 P1 置数,行扫描;

  3. 判断是否有键按下;

  4. 延时 10ms,软件去干扰;

  5. 确认按键按下 X = P1, 保存行扫描时有键按下时状态;

  6. 列扫描;

  7. 保存列扫描时有键按下时的状态;

  8. 取出键值

  9. 执行相应键值程序

本系统采用程序控制扫描方式对矩阵键盘进行按键检测,即只有当单片机空闲时,才调用键盘扫描子程序,响应键盘的输入请求。该系统中的矩阵键盘的行列线连接于STC89C52的P1端口,因为是4x4故单片机的P1端口被全部占用。键盘扫描程序自复位后就开始工作,时刻监视键盘,有无键按下。在监视键盘过程中,允许定时器 T0 中断,即同时动态显示数据和输出波形。一旦有键按下,先延时 10ms,去除键的抖动,然后关中断,不允许定时器 T0 发生中断。其框图如图 4-13示。

image-20230627111100017

键盘采用矩阵式键盘(如图 4-14 所示),由软件产生相应编码,再根据相应编码调用相应的子程序。编码产生原理:P1 口低四位表示行,高四位表示列。键盘所在的行和所在的列用 0 表示,其它的行和列用 1 表示。如第一行第一列编码值为0x07,第二行第三列编码值为0x0b。

image-20230627111413628

码值 对应处理程序
0 输入0
1 输入1
2 输入2
3 输入3
4 输入4
5 输入5
6 输入6
7 输入7
8 输入8
9 输入9
11 开始\完成输入

四、PWM 波发生程序设计

​ PWM 波可以理解为脉宽可调的方波,其产生方法和方波的产生方法类似。STC89C52 单片机具有三个 16 位计数器。通过控制其中某一个计数器和工作寄存器,而实现从STC89C52的任意输出口输出不同占空比的脉冲波形,能够有效的节省单片机的资源。我们应对有关定时器和中断的知识进行一定了解。

1、定时器

  1. STC-51系列中51子系列有两个16位的可编程定时/计数器:定时/计数器T0和定时/计数器T1,52子系列有三个,还有一个定时/计数器T2。
  2. 每个定时/计数器既可以对系统时钟计数实现定时,也可以对外部信号计数实现计数功能,通过编程设定来实现。
  3. 每个定时/计数器都有多种工作方式,其中T0有四种工作方式;T1有三种工作方式,T2有三种工作方式。通过编程设置其方式寄存器TMOD可设定定时器工作于某种方式,方式寄存器TMOD格式见图4-15示。

image-20230627141825839

GATE:门控信号。GATE=0,TRx=1时即可启动定时器/计数器工作,是一种自启动的方式;GATE=1,TRx=1, INTx =1时才可启动定时器/计数器工作。即是INTx引脚加高电平启动,是一种外启动方式。

C/T :定时或计数方式选择位,当C/T =1时工作于计数方式;当C/T =0时工作于定时方式。

M1、M0:为工作方式选择位,定时器/计数器的四种工作方式由M1M0设定,设定情况见表4-1示。

image-20230627142053931

  1. 每一个定时/计数器定时计数时间到时产生溢出,使控制寄存器TCON中相应的溢出位置位,溢出可通过查询或中断方式处理,控制寄存器格式见表4-2示。

image-20230627142411664

其中:
TF1:定时/计数器T1的溢出标志位,当定时/计数器T1计满时,由硬件使它置位,如中断允许则触发T1中断。进入中断处理后由内部硬件电路自动清除。
TR1:定时/计数器T1的启动位,可由软件置位或清零,当TR1=1时启动;TR1=0时停止。
TF0:定时/计数器T0的溢出标志位,当定时/计数器T0计满时,由硬件使它置位,如中断允许则触发T0中断。进入中断处理后由内部硬件电路自动清除。
TR0:定时/计数器T0的启动位,可由软件置位或清零,当TR0=1时启动;TR0=0时停止。

2、中断

​ STC89C52 有6个中断源:两个外部中断(INT0 和INT1),三个定时中断(定时器0、1、2)和一个串行中断。每个中断源都可以通过置位或清除特殊寄存器IE中的相关中断允许控制位分别使得中断源有效或无效。IE还包括一个中断允许总控制位EA,它能一次禁止所有中断。如表4-3所示,IE的6位是不可以用的。它们为STC89系列新产品预留。定时器2可以被寄存器T2CON中的TF2和EXF2的或逻辑触发。程序进入中断服务后,这些标志位都可以由硬件清0。实际上,中断服务程序必须判定是否是TF2 或EXF2激活中断,标志位也必须由软件清0。定时器0和定时器1标志位TF0 和TF1在计数溢出的那个周期的S5P2被置位。它们的值一直到下一个周期被电路捕捉下来。然而,定时器2的标志位TF2在计数溢出的那个周期的S2P2被置位,在同一个周期被电路捕捉下来。

image-20230627142856506

​ EA:中断允许总控位。EA=0,屏蔽所有的中断请求;EA=1,开放中断。
​ ET2:定时器/计数器 T2 的溢出中断允许位
​ ES:串行口中断允许位。
​ ET1:定时器/计数器 T1 的溢出中断允许位。
​ EX1:外部中断 INT1 的中断允许位。
​ ET0:定时器/计数器 T0 的溢出中断允许位。
​ EX0:外部中断 INT0 的中断允许位。

​ 通过对定时器和中断知识的介绍,可以确定该部分程序的实现方案,需要用到一个定时器通过改变 T10的计数值改变高电平的占空比。

image-20230627143521054

​ ATC89C52 的定时器/计数器为加计数器,计数初值是开始计数时计数器中的数。TH0和 TL0 是计数器 0 的高 8 位和低 8 位计数器,计算办法: TL0=(65536-C)%256 ,TH0=(65536-C)/256; 其中 C 为所要计数的次数即多长时间产生一次中断;TMOD 是计数器* 工作模式选择,0X01 表示选用模式 1,它有 16 位计数器,最大计数脉冲为 65536 最长时间为 1ms*65536=65.536ms。PWM 波程序流程图如图 4-16示。

五、液晶显示程序设计

​ 1602液晶也叫1602字符型液晶,它能显示2行字符信息,每行又能显示16个字符。它是一种专门用来显示字母、数字、符号的点阵型液晶模块。它是由若 干个5x7的点阵字符位组成,每个点阵字符位都可以用显示一个字符, 每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用。

​ 看懂时序图实际显示程序的基本,1602液晶显示屏的时序图如图4-17示。

image-20230627145000868

在上面的直线表示高电平,下面的直线表示低电平线的交叉处表示电平变化处,valid Data表示有效数据。

根据时序图可以帮助我们调用模块化示例在LCD屏上显示我们想要显示的内容。

六、LED流水灯程序设计

本系统要求流水灯LED灯的切换速度的快慢显示占空比大小,控制一个周期高电平的时间即改变占空比的大小,反映到LED灯点亮的时间即可完成显示占空比大小。将占空比乘以一个较大的因子能更好的通过切换速度显示占空比大小。其流程如图4-17示。

image-20230627150829455

五、结果及分析

5.1 系统仿真过程

​ 在Protues中,依照第四节介绍的硬件设计原理,新建设计并根据原理图选择所需器件连线可得本系统的仿真如图5-1示。

image-20230627153431671

​ 根据软件程序设计,系统初始状态占空比为50%,液晶屏显示‘SCM course tasks,Dutyfactor: 50%’,仿真情况如图5-2示。

image-20230627154253311

​ PWM占空比可调仿真测试:占空比50%,仿真结果如图5-3示。

image-20230627155017409

​ 按键调整占空比可得到不同占空比的PWM 波,仿真波形如图5-4示。

image-20230627155352970

image-20230627155643045

image-20230627160026938

仿真结果分析:

​ 通过Protues仿真结果,该系统可以完成实现0-99%占空比连续可调,并可以通过流水灯的切换速度展示占空比的大小。流水灯展现占空比的大小具体表现为随着占空比的不断增大,流水灯LED灯之间的切换速度逐渐变慢,这和我们的软件程序相符合,占空比越大,延迟时间越长,切换速度变慢。系统通过识别按键K12开始/结束占空比的调整,这在该系统中完美的实现,并通过不通按键对应的键值计算出占空比的大小显示在1602上,可以说该系统已经完美的完成了设计的任务要求。

5.2 系统实物测试

使用Keil通过USB接口电路烧录到普中开发板,观察LCD1602液晶屏的显示,以及流水灯的切换速度,经过测试得到如图5-5示的实验结果。

image-20230627210829793

image-20230627211226584

image-20230627211439387

image-20230627211658598

image-20230627211844952

实物测试结果分析:

​ 实物结果与仿真测试完全相符,可以清晰直观的从单片机控制的1602液晶屏直观的看见占空比连续可调。随着占空比的不断增大,实物下端的流水灯LED小灯切换速度明显变小,这直观的反应出占空比的增大。综上所述,该系统的仿真和实物都完美运行。

六、总结

​ 本次单片机课程设计的题目为基于51单片机占空比可调的控制系统设计与实现,要求实现占空比连续可调并通过lcd显示占空比的数值,通过跑马灯led的切换速度的快慢显示占空比大小。通过此次课程设计,我获得了一些宝贵的经验和收获。

​ 首先,在进行项目设计过程中,要保持清晰的思路,认真分析题目,明确需要实现的功能和要求。通过详细的设计思路和步骤,合理的分工协作,有助于提高项目的效率和实现质量。同时,在设计过程中要注意对各个元器件之间的调配和协调,保证整个系统的稳定和可靠性,避免出现不必要的问题和故障。

​ 其次,在实践中,遇到了许多问题,比如占空比的测量和调试问题,采用的传感器的不准确性问题,以及程序的编写问题等。在这些问题出现时,我们要保持耐心,认真分析和调试,采用合理的方法进行解决。这些问题的解决过程中,不仅提高了我们自身的实践能力,更锻炼了我们的合作能力和创新意识。

​ 最后,在本次课程设计中,我们成功地完成了任务,并取得了不错的成绩。通过此次课程设计,我们不仅学习了单片机的基本概念和知识,也掌握了单片机的编程思路和实践技巧,更重要的是,锻炼了我们的动手实践和解决问题的能力,对今后的学习和工作都具有积极的意义。

​ 总之,本次单片机课程设计是一次难得的机会,通过这次课程的学习和实践,我们获得了不少经验和收获,不仅扎实了我们自身的专业基础和实践能力,也为我们今后的学习和工作奠定了坚实的基础。

参考文献

[1] 汪宁.基于 FPGA 的 PWM 发生器的研究与设计[D]. 山东大学 , 2018

[2] 钟美鹏, 郑水英, 潘晓弘.直联式空压机 PWM 变占空比控制[J].农业机械学报 , 2019

[3] 陈桂友著.增强型8051单片机实用开发技术[M] .北京航空航天大学出版社,2009.

[4] 王宜怀.单片机原理及其嵌入式应用教程[M],北京希望电子出版社,2002.

[5] Mark Nelson著.潇湘工作室译.串行通信开发指南[M],中国水利水电出版社,2002.

[6] 宏晶科技.STC12C5A60S2单片机器件手册[M],2009

附录:程序源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//mian.c
#include <reg52.h>
#include <stdio.h>
#include "1602.h"
#include "delay.h"
#include "key.h"

#define uchar unsigned char
#define uint unsigned int
#define LED P2

sbit PWMOUT = P3^3;//PWM输出引脚

uchar HighRH = 0; //高电平重载值的高字节
uchar HighRL = 0; //高电平重载值的低字节
uchar LowRH = 0; //低电平重载值的高字节
uchar LowRL = 0; //低电平重载值的低字节
uchar led_pos = 0; //定义当前点亮的LED的位置

uchar flagF=0;//输入频率标志位
uchar flagD=0;//输入占空比标志位
void OpenPWM(uint fr, uint dc);
void ClosePWM();
void LED_Flow(unsigned int dc);


void main()
{
uint num,i;
uint freq=100;//初始频率
uint duty=50;//初始占空比
unsigned char temp[16];


LCD_Init(); //初始化液晶屏

LCD_Clear();
sprintf(temp,"SCM course tasks");
LCD_Write_String(0,0,temp);

sprintf(temp,"Dutyfactor: %d",duty);
LCD_Write_String(0,1,temp);
LCD_Write_Char(14,1,'%');

EA=1;

while (1)
{
num = key_matrix_flip_scan();
if(num!=16)
{
EA=0;//关中断

/*设置占空比*/
if(num==11 && flagD==0) //输入占空比
{
LCD_Clear();
LCD_Write_String(0,0,"OUTD:");
flagD=1;
duty=0;
i=0;

}
else if(num<10 && flagD==1) //数据转换
{
duty=duty*10+num;

i++;
LCD_Write_Char(i+4,0,num+0x30);

}

else if(num==11 && flagD==1) //占空比确认
{
EA=1;
flagD=0;
LCD_Clear();

sprintf(temp,"SCM course tasks");
LCD_Write_String(0,0,temp);

sprintf(temp,"Dutyfactor: %d",duty);
LCD_Write_String(0,1,temp);
LCD_Write_Char(14,1,'%');
}

}


OpenPWM(freq, duty);
LED_Flow(duty); //控制流水灯
delay_ms(1000);
// ClosePWM();

}
}
/* 配置并启动PWM,fr-频率,dc-占空比 */
void OpenPWM(unsigned int fr, unsigned int dc)
{
unsigned int high, low;
unsigned long tmp;

tmp = ((11059200/12) / fr) ; //计算一个周期所需的计数值
high = (tmp*dc) / 100; //计算高电平所需的计数值(一个周期所需的计数值*占空比)
low = tmp - high; //计算低电平所需的计数值(一个周期所需的计数值-高电平计数值)
high = 65536 - high + 12; //计算高电平的重载值并补偿中断延时
low = 65536 - low + 12; //计算低电平的重载值并补偿中断延时
HighRH = high/256; //高电平重载值拆分为高低字节
HighRL = high%256;
LowRH = low/256; //低电平重载值拆分为高低字节
LowRL = low%256;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0为模式1
TH0 = HighRH; //加载T0重载值
TL0 = HighRL;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
PWMOUT = 1; //输出高电平
}
/* 关闭PWM */
void ClosePWM()
{
TR0 = 0; //停止定时器
ET0 = 0; //禁止中断
PWMOUT = 1; //输出高电平
}

/* T0中断服务函数,产生PWM输出
当PWMOUT=1时,装载低电平值并输出低电平
当PWMOUT=0时,装载高电平值并输出高电平
*/
void InterruptTimer0() interrupt 1
{
if (PWMOUT == 1) //当前输出为高电平时,装载低电平值并输出低电平
{
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}
else //当前输出为低电平时,装载高电平值并输出高电平
{
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}

/* 控制LED流动,dc-占空比 */
void LED_Flow(unsigned int dc)
{
unsigned int delay_time;

LED = ~(1 << led_pos); //点亮当前位置的LED

delay_time = dc*100; //根据占空比计算延迟时间
delay_ms(delay_time); //延迟

led_pos = (led_pos + 1) % 8; //更新当前点亮的LED的位置
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//key.c
#include <reg52.h>
#include "key.h"
#include "delay.h"
#define KEY_MATRIX_PORT P1

typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;


u8 key_matrix_flip_scan(void)
{
static u8 key_value=0;

KEY_MATRIX_PORT=0x0f;//给所有行赋值0,列全为1
if(KEY_MATRIX_PORT!=0x0f)//判断按键是否按下
{
delay_ms(5);//消抖
if(KEY_MATRIX_PORT!=0x0f)
{
//测试列
KEY_MATRIX_PORT=0x0f;
switch(KEY_MATRIX_PORT)//保存行为0,按键按下后的列值
{
case 0x07: key_value=0;break;
case 0x0b: key_value=1;break;
case 0x0d: key_value=2;break;
case 0x0e: key_value=3;break;
}
//测试行
KEY_MATRIX_PORT=0xf0;
switch(KEY_MATRIX_PORT)//保存列为0,按键按下后的键值
{
case 0x70: key_value=key_value;break;
case 0xb0: key_value=key_value+4;break;
case 0xd0: key_value=key_value+8;break;
case 0xe0: key_value=key_value+12;break;
}
while(KEY_MATRIX_PORT!=0xf0);//等待按键松开
}
}
else
key_value=16;

return key_value;
}
1
2
3
4
5
6
7
//key.h
#ifndef __KEY_H__
#define __KEY_H__

unsigned char key_matrix_flip_scan(void);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//1602.c
#include "1602.h"
#include "delay.h"

#define CHECK_BUSY

sbit RS = P2^6; //定义端口
sbit RW = P2^5;
sbit EN = P2^7;

#define RS_CLR RS=0
#define RS_SET RS=1

#define RW_CLR RW=0
#define RW_SET RW=1

#define EN_CLR EN=0
#define EN_SET EN=1

#define DataPort P0

/*------------------------------------------------
判忙函数
------------------------------------------------*/
bit LCD_Check_Busy(void)
{
#ifdef CHECK_BUSY
DataPort= 0xFF;
RS_CLR;
RW_SET;
EN_CLR;
_nop_();
EN_SET;
return (bit)(DataPort & 0x80);
#else
return 0;
#endif
}
/*------------------------------------------------
写入命令函数
------------------------------------------------*/
void LCD_Write_Com(unsigned char com)
{
// while(LCD_Check_Busy()); //忙则等待
delay_ms(5);
RS_CLR;
RW_CLR;
DataPort= com;
EN_SET;
delay_ms(50);
EN_CLR;
}
/*------------------------------------------------
写入数据函数
------------------------------------------------*/
void LCD_Write_Data(unsigned char Data)
{
//while(LCD_Check_Busy()); //忙则等待
delay_ms(5);
RS_SET;
RW_CLR;

DataPort= Data;
EN_SET;
delay_ms(50);
EN_CLR;
}


/*------------------------------------------------
清屏函数
------------------------------------------------*/
void LCD_Clear(void)
{
LCD_Write_Com(0x01);
delay_ms(500);
}
/*------------------------------------------------
写入字符串函数
------------------------------------------------*/
void LCD_Write_String(unsigned char x,unsigned char y,unsigned char *s)
{

while (*s)
{
LCD_Write_Char(x,y,*s);
s ++; x++;
}
}
/*------------------------------------------------
写入字符函数
------------------------------------------------*/
void LCD_Write_Char(unsigned char x,unsigned char y,unsigned char Data)
{
if (y == 0)
{
LCD_Write_Com(0x80 + x);
}
else
{
LCD_Write_Com(0xC0 + x);
}
LCD_Write_Data( Data);
}
/*------------------------------------------------
初始化函数
------------------------------------------------*/
void LCD_Init(void)
{
LCD_Write_Com(0x38); /*显示模式设置*/
delay_ms(5);
LCD_Write_Com(0x38);
delay_ms(5);
LCD_Write_Com(0x38);
delay_ms(5);
LCD_Write_Com(0x38);
LCD_Write_Com(0x08); /*显示关闭*/
LCD_Write_Com(0x01); /*显示清屏*/
LCD_Write_Com(0x06); /*显示光标移动设置*/
delay_ms(5);
LCD_Write_Com(0x0C); /*显示开及光标设置*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//1602.h

#include<reg52.h>
#include<intrins.h>

#ifndef __1602_H__
#define __1602_H__


bit LCD_Check_Busy(void) ;

void LCD_Write_Com(unsigned char com) ;

void LCD_Write_Data(unsigned char Data) ;

void LCD_Clear(void) ;

void LCD_Write_String(unsigned char x,unsigned char y,unsigned char *s) ;

void LCD_Write_Char(unsigned char x,unsigned char y,unsigned char Data) ;

void LCD_Init(void) ;

#endif
1
2
3
4
5
6
7
8
//delay.c
#include<reg52.h>
void delay_ms(unsigned int x)
{
unsigned int i,j;
for(i=x;i>0;i--)
for(j=120;j>0;j--);
}
1
2
3
4
5
6
7
//delay.h
#ifndef __DELAY_H__
#define __DELAY_H__

void delay_ms( unsigned int x);

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//key.c
#include <reg52.h>
#include "key.h"
#include "delay.h"
#define KEY_MATRIX_PORT P1

typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;


u8 key_matrix_flip_scan(void)
{
static u8 key_value=0;

KEY_MATRIX_PORT=0x0f;//给所有行赋值0,列全为1
if(KEY_MATRIX_PORT!=0x0f)//判断按键是否按下
{
delay_ms(5);//消抖
if(KEY_MATRIX_PORT!=0x0f)
{
//测试列
KEY_MATRIX_PORT=0x0f;
switch(KEY_MATRIX_PORT)//保存行为0,按键按下后的列值
{
case 0x07: key_value=0;break;
case 0x0b: key_value=1;break;
case 0x0d: key_value=2;break;
case 0x0e: key_value=3;break;
}
//测试行
KEY_MATRIX_PORT=0xf0;
switch(KEY_MATRIX_PORT)//保存列为0,按键按下后的键值
{
case 0x70: key_value=key_value;break;
case 0xb0: key_value=key_value+4;break;
case 0xd0: key_value=key_value+8;break;
case 0xe0: key_value=key_value+12;break;
}
while(KEY_MATRIX_PORT!=0xf0);//等待按键松开
}
}
else
key_value=16;

return key_value;
}
1
2
3
4
5
6
#ifndef __KEY_H__
#define __KEY_H__

unsigned char key_matrix_flip_scan(void);

#endif