本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:C51是针对8051微控制器系列设计的高级C语言扩展,通过特殊功能寄存器提供了硬件I/O访问能力。本文档深入讨论了C51中经常使用的子函数,覆盖了输入/输出操作、延时函数、中断处理、定时器/计数器操作、串行通信、ADC读取、显示与键盘控制、存储器操作、数学运算和错误处理等领域。这些子函数为8051的开发提供了便利,初学者可以直接调用以提高开发效率。
c51常用子函数自己总结的

1. C51语言概述与8051硬件I/O访问

1.1 C51语言简介

C51语言,也称为Keil C,是一种专门用于8051微控制器的高级编程语言。它基于标准C语言进行扩展,允许嵌入式开发人员能够使用C语言的高级特性来编写8051的固件。由于C51保留了C语言的大部分语法规则,因此它非常适合作为初学者的入门语言。8051系列微控制器是目前使用最广泛的单片机之一,具有价格低廉、功能强大、功耗低等特点。

1.2 8051硬件I/O访问基础

在8051微控制器中,I/O端口是微控制器与外部世界交互的桥梁。C51语言提供了直接访问I/O端口的功能,使得操作这些端口就像操作普通的C语言变量一样简单。8051具有四个并行的8位I/O端口,分别是P0、P1、P2和P3。通过操作这些端口,我们可以控制或读取连接到它们的外部设备。

#include <reg51.h>  // 包含8051寄存器定义的头文件

void main() {
    P1 = 0xFF;  // 将P1端口的所有引脚设置为高电平
    // 其他代码...
}

在上面的示例中,通过简单地赋值操作,就可以控制P1端口的电平状态。这为开发人员提供了一种快速且直观的方法来控制连接到I/O端口的设备,如LED灯、继电器、传感器等。后续章节将详细介绍如何实现更复杂的I/O操作和子函数设计。

2. 输入/输出操作子函数实现

2.1 基础I/O操作

2.1.1 端口读写函数

在8051微控制器中,端口的读写是最基础的I/O操作。端口可以被配置为输入或输出,从而控制外部设备。我们常用的端口是P0、P1、P2和P3,每一个端口都是8位的,可以通过操作相应的特殊功能寄存器(SFR)来完成端口读写操作。

/* 端口读取函数 */
unsigned char PortRead(unsigned char port) {
    return port; // 此处简化的返回实际应为特定端口的值
}

/* 端口写入函数 */
void PortWrite(unsigned char port, unsigned char value) {
    port = value; // 此处简化的操作实际应为对特定端口的赋值
}

在上述代码中, PortRead 函数用于从指定的端口读取值,而 PortWrite 函数用于向指定端口写入值。在实际的开发中,需要根据硬件的具体配置来操作特殊功能寄存器。比如,为了确保数据的准确读取,可能需要使用位操作来单独控制端口的某一位。

2.1.2 位操作与控制

位操作通常用于对端口的某一位进行精确控制,比如控制LED灯或读取按键状态。8051微控制器提供了位寻址的能力,允许程序员对单个位进行读写操作。

/* 位操作函数 */
void SetBit(unsigned char port, unsigned char bitNumber) {
    port |= (1 << bitNumber); // 将端口的特定位设为高电平
}

void ClearBit(unsigned char port, unsigned char bitNumber) {
    port &= ~(1 << bitNumber); // 将端口的特定位设为低电平
}

SetBit 函数中,我们使用按位或操作符 | 将端口的特定位设置为高电平,而在 ClearBit 函数中,我们使用按位与操作符 & 和取反操作符 ~ 来将特定位设置为低电平。

2.2 复杂I/O应用

2.2.1 液晶显示屏(LCD)控制

液晶显示屏(LCD)是嵌入式系统常用的显示设备,它可以显示字符和图形。控制LCD涉及到多个步骤,包括初始化、发送命令、发送数据和读取状态。

/* LCD 控制函数 */
void LCD_Init() {
    // 发送初始化命令序列到LCD
}

void LCD_WriteCommand(unsigned char command) {
    // 写命令到LCD
}

void LCD_WriteData(unsigned char data) {
    // 写数据到LCD
}

在上述代码中, LCD_Init 函数用于初始化LCD,该函数在LCD上电后需要首先被调用。 LCD_WriteCommand LCD_WriteData 函数分别用于发送命令和数据到LCD。在实际的应用中,还需要实现读取LCD状态的函数,以确保数据被正确写入。

2.2.2 LED显示与控制

LED显示通常是指通过微控制器来控制一组LED灯的亮灭。使用位操作可以方便地控制每个LED灯的状态。

/* LED控制函数 */
void LED_Control(unsigned char pattern) {
    // 根据传入的模式控制LED灯
}

在这个函数中, pattern 参数是一个字节,每一位对应一个LED灯的状态。通过位操作,我们可以设置每个LED的状态。例如,使用 SetBit ClearBit 函数,我们可以点亮或熄灭特定的LED灯。

以上我们介绍了基础I/O操作和复杂I/O应用。在实现这些操作时,需要精确控制微控制器的端口,合理组织代码,确保程序的可靠性和稳定性。接下来,我们将探讨如何设计和应用延时函数。

3. 延时函数的设计与应用

3.1 软件延时实现

3.1.1 精确延时计算

在没有硬件定时器支持的情况下,软件延时成为了一种简单且实用的延时方法。精确的软件延时依赖于程序中执行的指令周期计数。在8051架构中,每条指令的执行周期是固定的,这使得计算软件延时成为可能。

通常,我们可以使用汇编语言中的 NOP (No Operation,无操作)指令来构建延时循环,因为 NOP 执行周期是1个机器周期。以下是一个使用汇编语言实现的延时函数示例:

; 延时函数示例
DELAY:  MOV R2, #250       ; R2是外层循环计数器
OUTER_LOOP: 
        MOV R1, #250       ; R1是内层循环计数器
INNER_LOOP: 
        NOP                ; 执行一个NOP指令,1个机器周期
        DJNZ R1, INNER_LOOP ; 内层循环递减并检查是否为零,如果不为零则跳转回内层循环的起始
        DJNZ R2, OUTER_LOOP ; 外层循环递减并检查是否为零,如果不为零则跳转回外层循环的起始
        RET                ; 返回调用函数处

在这个例子中, R2 R1 两个寄存器被用作循环计数器,创建了一个双层循环结构。 NOP 指令用于消耗时间,而 DJNZ (Decrement and Jump if Not Zero,递减并跳转如果非零)指令用于控制循环次数。通过调整 R2 R1 的初始值,可以改变延时的长度。

为了精确计算延时的长度,我们需要知道8051单片机的时钟频率,因为每个机器周期对应于12个振荡周期(对于标准8051)。以12MHz的晶振为例,每个机器周期大约是1微秒。因此,如果 R1 R2 都设置为250,那么总的延时大约是:

(250 循环次数 * 250 内层循环次数 + 15 机器周期开销) * 1 微秒/机器周期 = 62515 微秒

这里的15是进入和退出循环的开销,通常与寄存器操作和跳转指令相关。

3.1.2 延时优化技巧

使用软件延时的方法虽然简单,但也有它的缺点,最主要的是占用CPU资源。在延时期间,CPU无法做其它事情,这在很多实时任务中是不可接受的。此外,软件延时的准确性依赖于程序的其它部分不干扰延时循环,这在多任务环境中很难保证。

为了改善这一情况,可以考虑以下优化技巧:

  1. 预计算延时 :对于常见的延时需求,可以预先计算出需要的循环次数,并将这些预计算值存放在查找表中。这样,可以根据需要选择合适的延时长度,而不必每次都进行复杂的计算。
  2. 循环展开 :在某些情况下,可以减少循环的开销,通过将多个 NOP 指令合并为一条指令,或者在内循环中减少 DJNZ 指令的使用次数来减少跳转。
  3. 多任务处理 :如果应用允许,可以在延时期间允许CPU处理其他任务,例如使用中断服务程序。这样,即使软件延时正在执行,系统也能处理其它的事件。

3.2 硬件定时器延时

3.2.1 定时器初始化与配置

相比软件延时,硬件定时器提供了更为精确和高效的方法来进行时间测量。8051单片机内部提供了定时器/计数器,可以在硬件层面进行时间管理,而不占用CPU的处理时间。

硬件定时器通常需要进行初始化和配置,以设定定时器的工作模式和计数值。在8051中,定时器/计数器可以工作在模式0(13位定时器模式)、模式1(16位定时器模式)、模式2(8位自动重装载定时器模式)或模式3(仅适用于定时器0的分裂定时器模式)。下面是一个初始化定时器0为模式1的示例代码:

#include <reg51.h> // 包含8051寄存器定义的头文件

void Timer0_Init() {
    TMOD &= 0xF0; // 清除定时器0模式位
    TMOD |= 0x01; // 设置定时器0为模式1(16位定时器)
    TH0 = 0x00;   // 设置定时器高字节初值
    TL0 = 0x00;   // 设置定时器低字节初值
    TR0 = 1;      // 启动定时器0
}

在这个例子中,我们首先包含了 reg51.h 头文件,该文件包含了8051单片机的SFR(Special Function Register,特殊功能寄存器)定义。然后定义了 Timer0_Init 函数,用于初始化定时器0。我们首先通过位操作来设置定时器模式寄存器 TMOD ,将定时器0设置为模式1。接着设置定时器初值,并启动定时器。

3.2.2 中断方式下的延时应用

当定时器配置完成后,可以通过定时器中断来实现延时功能。定时器溢出(即计数器从初值计数到最大值后回滚到零)时会触发中断。在中断服务程序中可以实现特定的任务。

以下是一个使用定时器中断实现延时的完整示例:

#include <reg51.h>

volatile unsigned int timer0_counter = 0;

void Timer0_ISR() interrupt 1 using 1 {
    timer0_counter++; // 在中断服务程序中增加计数器
    if (timer0_counter >= 500) { // 假设我们希望延时500个定时器中断
        timer0_counter = 0;
        // 执行需要延时后的任务
    }
}

void Timer0_Init() {
    TMOD |= 0x01; // 设置定时器0为模式1(16位定时器)
    TH0 = 0x00;   // 设置定时器高字节初值
    TL0 = 0x00;   // 设置定时器低字节初值
    ET0 = 1;      // 启用定时器0中断
    EA = 1;       // 启用全局中断
    TR0 = 1;      // 启动定时器0
}

void main() {
    Timer0_Init(); // 初始化定时器0
    while(1) {
        // 主循环
    }
}

在这个例子中,我们定义了一个全局变量 timer0_counter 作为定时器中断计数器,并在定时器中断服务程序中增加它。当计数器达到预设的阈值时,执行延时后的任务。使用中断来实现延时不仅可以进行精确的时间控制,而且允许CPU在等待期间执行其它任务,大大提高了程序的效率和响应性。

4. 中断服务子函数结构与实现

4.1 中断系统基础

4.1.1 中断向量与优先级设置

中断向量是中断服务例程的入口地址,当中断发生时,CPU会根据中断向量表中的地址跳转到相应的中断处理函数中执行。8051微控制器中,中断向量表的设置是通过特定的内存地址进行的,例如外部中断0和定时器0的中断向量分别是0003H和000BH。

中断优先级是为了解决多个中断同时发生时,CPU应该首先响应哪个中断的问题。8051微控制器支持两级优先级,即高优先级和低优先级。中断优先级的设置通过中断优先级寄存器(IP)进行配置,IP寄存器中的每一位对应一个中断源,通过设置该位为1或0来确定是否赋予高优先级。

4.1.2 中断控制寄存器解析

在8051微控制器中,中断控制涉及的寄存器主要有两个:中断使能寄存器(IE)和中断优先级寄存器(IP)。IE寄存器用于开启或关闭特定中断源的中断请求。IE寄存器中的EA位是全局中断使能位,只有当EA为1时,其他中断使能位才起作用。EA为0时,所有中断都会被屏蔽。

IP寄存器则是用于设置中断优先级,每个中断源对应IP寄存器中的一个位。例如,如果IP寄存器的PS位被设置为1,则表示外部中断0具有高优先级。当多个中断同时发生时,具有高优先级的中断会被优先处理。

4.2 中断服务程序设计

4.2.1 响应外部中断

要响应外部中断,首先需要设置中断允许位,并配置中断优先级。以外部中断0为例,首先需要将IE寄存器中的EA和EX0位设置为1,然后根据需要配置IP寄存器来设置优先级。当外部中断0的中断请求被触发时,如果中断条件符合,CPU会跳转到0003H开始的地址执行中断服务例程。

void External0_ISR(void) interrupt 0  // 中断服务例程,外部中断0的中断向量是0003H
{
    // 中断处理代码
    // ...
}

4.2.2 多中断源处理策略

在实际应用中,可能存在多个中断源同时请求的情况。处理多中断源的关键在于合理设置中断优先级和在中断服务程序中使用一些策略来保证系统的稳定和响应的及时性。

在8051微控制器中,中断优先级可以预先设定,但在中断服务程序中,还可以利用软件手段进行动态的优先级管理。例如,在一个中断服务程序中,可以通过查询其他中断标志位的状态来判断是否有更高优先级的中断需要处理,并在完成当前中断后主动调用更高优先级的中断服务函数。

此外,为了防止频繁的中断请求影响系统的正常工作,可以采用中断屏蔽的方法来暂时忽略某些中断源。在处理完一个高优先级的中断后,再根据实际情况决定是否开启之前屏蔽的中断源。

void Multiple_ISR(void) interrupt 0 // 假设这是一个处理多中断源的中断服务例程
{
    // 检查更高优先级中断标志位,若有必要,调用该中断的服务函数
    if (HighPriorityInterruptFlag) {
        HighPriority_ISR();
    }
    // 处理当前中断的逻辑
    // ...
    // 根据需要开启之前屏蔽的中断源
    EA = 1;
}

在上述代码示例中,我们展示了如何在处理一个中断时,检查并响应更高优先级的中断请求。这是实现多中断源处理策略的一种基本方法。在实际应用中,还需要根据系统的具体需求,设计更加复杂的中断处理逻辑和优先级分配策略。

5. 定时器/计数器操作子函数使用

5.1 定时器模式配置

5.1.1 定时器计数模式

定时器/计数器是8051微控制器中用于计时和计数的专用硬件资源。它们可以配置为不同的工作模式以满足不同的应用场景需求。通常,定时器/计数器工作在定时器模式时,可以用于精确的延时或时间测量;在计数器模式时,则用于计数外部事件。

在定时器模式下,定时器对内部机器周期进行计数。当定时器的计数值达到预设的值(称为定时器重装值)时,会产生一个定时器溢出中断,之后定时器会被自动重装并继续计数。要配置定时器工作在定时器模式,需要对TMOD寄存器进行设置。

以8051的定时器0为例,下面是一个代码示例及其解析:

#include <reg51.h>

void Timer0_Init(void) {
    TMOD |= 0x01;  // 设置定时器0为模式1(16位定时器模式)
    TH0 = 0xFC;    // 设置定时器重装值高字节
    TL0 = 0x18;    // 设置定时器重装值低字节
    TR0 = 1;       // 启动定时器0
}

void Timer0_ISR(void) interrupt 1 {
    // 中断服务例程,定时器溢出时调用
    TH0 = 0xFC;    // 重新加载定时器重装值高字节
    TL0 = 0x18;    // 重新加载定时器重装值低字节
    // 添加用户代码,例如:翻转LED状态
}

在这段代码中,我们首先通过或操作将TMOD寄存器的低四位设置为0001,这表明定时器0工作在模式1(16位定时器模式)。接着,我们设置了定时器的重装值,TH0和TL0分别对应16位定时器的高八位和低八位。最后,通过设置TR0位为1来启动定时器。

5.1.2 定时器溢出与中断

定时器溢出是定时器达到重装值并回绕至0的事件。此事件通常用于产生周期性的中断,以执行特定的任务或维持一个周期性的操作。

定时器溢出中断的设置包括以下步骤:

  • 设置定时器模式和重装值。
  • 配置中断允许寄存器EA和定时器中断使能位ET0。
  • 编写中断服务例程。

中断服务例程应该简单高效,以避免过长时间阻塞其他中断。在例程中,通常会重新加载定时器的重装值,并执行需要周期性执行的任务。

5.2 计数器功能应用

5.2.1 计数器初始化与控制

当定时器/计数器配置为计数器模式时,它们可以对外部事件进行计数。例如,可以用来统计外部脉冲的数量,或者测量外部信号的频率和周期。

计数器模式下,需要将TMOD寄存器的相应位设置为计数器模式,并将定时器的计数输入引脚(如T0或T1)连接到外部事件源。

下面是一个配置定时器0为计数器模式的代码示例及其逻辑分析:

#include <reg51.h>

void Counter0_Init(void) {
    TMOD |= 0x10;  // 设置定时器0为模式1(16位计数器模式)
    TH0 = 0x00;    // 计数器初值设置为0
    TL0 = 0x00;
    TR0 = 1;       // 启动计数器0
}

void Counter0_ISR(void) interrupt 1 {
    // 中断服务例程,计数器溢出时调用
    // 这里可以执行相应的处理,例如:记录计数器溢出次数
}

在这个例子中,TMOD寄存器的低四位被设置为0001,表明定时器0工作在模式1的计数器模式。计数器的初值被设置为0,允许从0开始计数。TR0位被设置为1,启动计数器。

5.2.2 计数器在测量中的应用

计数器的典型应用之一是在时间间隔测量中。例如,如果我们想要测量两个脉冲之间的时间间隔,我们可以使用计数器来实现。

为了在测量过程中防止溢出,需要合理选择计数器的初值,并根据预估的测量范围设置重装值。计数器溢出中断可以用来处理溢出事件,通过记录溢出次数,可以计算出大于计数器容量的总时间间隔。

在实际应用中,通过精确测量外部事件之间的时间间隔,可以实现距离测量、速度测量等精密测量功能。例如,在基于8051的测距设备中,使用超声波传感器发出信号,并通过计数器测量从发射到接收的时间间隔,进而计算出距离。

6. 串行通信子函数:初始化与数据传输

6.1 串行通信基础知识

6.1.1 串口通信协议与设置

串行通信是一种在两个或多个设备之间顺序发送和接收数据的方法。在嵌入式系统中,串口通信经常用于与PC或其他设备进行数据交换。8051微控制器内置了一个全双工的串行口,可以通过它与其他串行设备进行通信。为了进行通信,首先需要设置串口的工作协议,包括确定数据位、停止位和校验位等参数。串口初始化通常涉及以下几个步骤:

  1. 配置串行控制寄存器SCON,设定通信模式。
  2. 配置定时器,用作波特率发生器。
  3. 启用串行中断(如果需要)。

下面是一个简单的8051串口初始化代码示例,设置为模式1(8位UART,可变波特率):

void Serial_Init() {
    SCON = 0x50;  // 设置模式1,8位数据,可变波特率
    TMOD |= 0x20; // 设置定时器1为8位自动重装模式
    TH1 = 0xFD;   // 设置波特率9600
    TR1 = 1;      // 启动定时器1
    TI = 1;       // 设置发送中断标志,准备发送第一个字符
}

6.1.2 波特率的计算与配置

波特率是串行通信中每秒传输的符号数。波特率的配置与微控制器的时钟频率和定时器的配置有关。在模式1中,波特率的计算公式为:

波特率 = 定时器溢出率 / 32

对于11.0592MHz的晶振,可以通过定时器来生成接近9600波特率的频率。具体设置为:

TH1 = 256 - (晶振频率 / (12 * 32 * 目标波特率))
TH1 = 256 - (11059200 / (12 * 32 * 9600)) ≈ 0xFD

6.2 数据收发处理

6.2.1 发送子函数实现

为了发送数据,可以使用一个简单的函数来实现数据的串行发送。以下是使用查询方式发送单个字节数据的例子:

void Serial_SendByte(unsigned char byte) {
    SBUF = byte; // 将数据放入到串行缓冲寄存器
    while (!TI); // 等待发送完成
    TI = 0;     // 清除发送完成标志,为发送下一个字节做准备
}

6.2.2 接收子函数实现与缓冲策略

接收数据通常需要考虑缓冲策略,以避免数据溢出。8051单片机提供了串行接收中断,可以用来处理接收到的数据。以下是一个带有缓冲的串行接收处理例子:

#define RX_BUFFER_SIZE 10
unsigned char rx_buffer[RX_BUFFER_SIZE];
unsigned char rx_index = 0;

void Serial_Receive() interrupt 4 {
    if (RI) {
        RI = 0; // 清除接收中断标志
        rx_buffer[rx_index] = SBUF; // 读取接收到的字节
        rx_index = (rx_index + 1) % RX_BUFFER_SIZE; // 更新索引
    }
}

void Serial_InitReceiver() {
    SCON = 0x50; // 依然是模式1
    // RI和TI初始化,剩余设置已在发送函数中进行
}

以上代码段展示了如何初始化串行通信,发送数据,以及使用中断方式接收数据。需要注意的是,实际应用中,数据的发送和接收通常会更复杂,并涉及到错误检测和重传机制等更高级的特性。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:C51是针对8051微控制器系列设计的高级C语言扩展,通过特殊功能寄存器提供了硬件I/O访问能力。本文档深入讨论了C51中经常使用的子函数,覆盖了输入/输出操作、延时函数、中断处理、定时器/计数器操作、串行通信、ADC读取、显示与键盘控制、存储器操作、数学运算和错误处理等领域。这些子函数为8051的开发提供了便利,初学者可以直接调用以提高开发效率。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐