51单片机概述
51单片机是指Intel公司开发的MCS-51系列单片机及其兼容产品,是应用最广泛的8位单片机系列之一。其名称来源于该系列的第一款芯片8051。
主要特点:
-
8位CPU:处理数据宽度为8位
-
哈佛结构:程序存储器和数据存储器分开
-
40引脚DIP封装:标准双列直插式封装
-
4KB ROM:早期型号,现代兼容型号容量更大
-
128B RAM:内部数据存储器
-
32个I/O口:4个8位并行I/O端口
-
2个16位定时器/计数器
-
1个全双工串行通信口
-
5个中断源:2个外部中断,2个定时器中断,1个串口中断
常见51单片机型号
型号 | 主要特性 |
---|---|
8051 | 原始型号,4KB ROM,128B RAM |
8031 | 无片内ROM |
89C51 | Atmel公司产品,Flash ROM |
89S51 | 支持ISP在线编程 |
STC89C52 | 宏晶科技产品,8KB Flash,512B RAM |
STC12C5A60S2 | 增强型51,1T周期,60KB Flash,1280B RAM |
51单片机内部结构
1. CPU结构
-
算术逻辑单元(ALU):执行算术和逻辑运算
-
累加器(ACC):主要寄存器,用于运算和数据传输
-
B寄存器:用于乘除法运算
-
程序状态字(PSW):包含各种状态标志位
-
程序计数器(PC):16位,指向下一条要执行的指令
2. 存储器结构
-
程序存储器(ROM/Flash):存储程序代码
-
内部数据存储器(RAM):128字节(52系列为256字节)
-
特殊功能寄存器(SFR):控制各功能模块
-
外部扩展存储器:可扩展至64KB
3. 输入输出端口
-
P0口:8位开漏双向I/O口,兼作地址/数据总线
-
P1口:8位准双向I/O口
-
P2口:8位准双向I/O口,兼作高8位地址总线
-
P3口:8位准双向I/O口,具有第二功能
51单片机最小系统
一个能工作的51单片机最小系统需要:
-
单片机芯片(如STC89C52)
-
时钟电路:通常11.0592MHz晶振+两个30pF电容
-
复位电路:10kΩ电阻+10μF电容构成上电复位
-
电源电路:5V稳定电源,加滤波电容
L298N介绍
L298N是一种双H桥电机驱动芯片,能够驱动两个直流电机或一个步进电机,广泛应用于机器人、智能小车等项目中。
主要特性
-
工作电压:5V-35V
-
最大电流:2A(单桥)
-
逻辑电压:5V(兼容TTL电平)
-
驱动方式:双H桥驱动
-
可同时控制:2个直流电机或1个两相/四相步进电机
模块引脚说明
电源部分
-
+12V/VCC:电机驱动电源输入(5-35V)
-
GND:电源地
-
+5V:输出5V(当使能跳线帽接通时)或5V输入(当断开跳线帽时)
控制部分
-
ENA/ENB:PWM使能端(A/B通道)
-
IN1/IN2:A通道控制输入端
-
IN3/IN4:B通道控制输入端
与51单片机的连接方式
51单片机引脚连接L298N: - P1.0 → L298N ENA (PWM输入A) - P1.1 → L298N IN1 - P1.2 → L298N IN2 - (如果需要控制第二个电机) - P1.3 → L298N ENB (PWM输入B) - P1.4 → L298N IN3 - P1.5 → L298N IN4
电机控制逻辑
单个直流电机控制
IN1 | IN2 | ENA | 电机状态 |
---|---|---|---|
0 | 0 | X | 停止 |
1 | 0 | PWM | 正转(PWM调速) |
0 | 1 | PWM | 反转(PWM调速) |
1 | 1 | X | 刹车 |
PWM调速原理
通过改变ENA/ENB引脚PWM信号的占空比来调节电机速度:
-
占空比高 → 平均电压高 → 转速快
-
占空比低 → 平均电压低 → 转速慢
典型应用电路
-
电源部分:
-
电机电源与单片机电源共地
-
建议电机电源与逻辑电源分开供电
-
大容量滤波电容靠近L298N电源引脚
-
-
保护电路:
-
电机两端并联续流二极管
-
必要时添加散热片
-
proteus仿真图
代码实现
主函数
#include <REGX52.H>
#include <intrins.h>
#include "LCD1602.h"
#include "public.h"
#include "stdio.h"
char war[32];sbit IN1 = P1^1;
sbit IN2 = P1^2;
sbit ENA = P1^0; // PWM输出引脚unsigned char PWM = 0; // PWM占空比 0-100
bit DIR = 0; // 方向 0或1void Timer0Init() // 100us@11.0592MHz
{TMOD &= 0xF0; // 设置定时器模式TMOD |= 0x02; // 8位自动重装模式TH0 = 0xA4; // 重装值TL0 = 0xA4; // 初始值TR0 = 1; // 启动定时器ET0 = 1; // 使能定时器中断EA = 1; // 开启总中断
}void main()
{Timer0Init();LCD_Init();LCD_Clear();IN1 = 1;IN2 = 0; // 初始方向PWM =20; while(1){// 这里可以添加按键扫描或其他控制逻辑来改变PWM值// 例如:// if(按键加按下) PWM++;// if(按键减按下) PWM--;// if(按键方向按下) DIR = !DIR;if(P1_4 ==0){Delay_ms(100);if(P1_4 ==0) { PWM++; }}if(P1_5 ==0){Delay_ms(100);if(P1_5 ==0) { PWM--; }}if(P1_6 ==0){Delay_ms(100);if(P1_6 ==0) { DIR = !DIR; }}// 限制PWM范围if(PWM > 100) PWM = 100;if(PWM < 0) PWM = 0;// 设置方向if(DIR){IN1 = 1;IN2 = 0;}else{IN1 = 0;IN2 = 1;}sprintf(war,"PWM:%03d DIR:%d",(int)PWM,(int)DIR);LCD_ShowString(2,1,war); }
}void Timer0_ISR() interrupt 1
{static unsigned char count = 0;count++;if(count >= 100){count = 0;}if(count < PWM){ENA = 1; // 高电平期间电机通电}else{ENA = 0; // 低电平期间电机断电}
}
LCD1602.c
#include <REGX52.H>//引脚配置:
//sbit LCD_RS=P1^2;
//sbit LCD_RW=P1^3;
//sbit LCD_EN=P1^4;sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0//函数定义:
/*** @brief LCD1602延时函数,12MHz调用可延时1ms* @param 无* @retval 无*/
void LCD_Delay()
{unsigned char i, j;i = 2;j = 239;do{while (--j);} while (--i);
}/*** @brief LCD1602写命令* @param Command 要写入的命令* @retval 无*/
void LCD_WriteCommand(unsigned char Command)
{LCD_RS=0;LCD_RW=0;LCD_DataPort=Command;LCD_EN=1;LCD_Delay();LCD_EN=0;LCD_Delay();
}/*** @brief LCD1602写数据* @param Data 要写入的数据* @retval 无*/
void LCD_WriteData(unsigned char Data)
{LCD_RS=1;LCD_RW=0;LCD_DataPort=Data;LCD_EN=1;LCD_Delay();LCD_EN=0;LCD_Delay();
}/*** @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 无* @retval 无*/
void LCD_Init()
{LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动LCD_WriteCommand(0x01);//光标复位,清屏
}void LCD_Clear()
{LCD_WriteCommand(0x01);//光标复位,清屏
}
/*** @brief 在LCD1602指定位置上显示一个字符* @param Line 行位置,范围:1~2* @param Column 列位置,范围:1~16* @param Char 要显示的字符* @retval 无*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{LCD_SetCursor(Line,Column);LCD_WriteData(Char);
}/*** @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]);}
}///**
// * @brief 返回值=X的Y次方
// */
//int LCD_Pow(int X,int Y)
//{
// unsigned char i;
// int Result=1;
// for(i=0;i<Y;i++)
// {
// Result*=X;
// }
// return Result;
//}///**
// * @brief 在LCD1602指定位置开始显示所给数字
// * @param Line 起始行位置,范围:1~2
// * @param Column 起始列位置,范围:1~16
// * @param Number 要显示的数字,范围:0~65535
// * @param Length 要显示数字的长度,范围:1~5
// * @retval 无
// */
//void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
//{
// unsigned char i;
// LCD_SetCursor(Line,Column);
// for(i=Length;i>0;i--)
// {
// LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
// }
//}///**
// * @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
// * @param Line 起始行位置,范围:1~2
// * @param Column 起始列位置,范围:1~16
// * @param Number 要显示的数字,范围:-32768~32767
// * @param Length 要显示数字的长度,范围:1~5
// * @retval 无
// */
//void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
//{
// unsigned char i;
// unsigned int Number1;
// LCD_SetCursor(Line,Column);
// if(Number>=0)
// {
// LCD_WriteData('+');
// Number1=Number;
// }
// else
// {
// LCD_WriteData('-');
// Number1=-Number;
// }
// for(i=Length;i>0;i--)
// {
// LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
// }
//}///**
// * @brief 在LCD1602指定位置开始以十六进制显示所给数字
// * @param Line 起始行位置,范围:1~2
// * @param Column 起始列位置,范围:1~16
// * @param Number 要显示的数字,范围:0~0xFFFF
// * @param Length 要显示数字的长度,范围:1~4
// * @retval 无
// */
//void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
//{
// unsigned char i,SingleNumber;
// LCD_SetCursor(Line,Column);
// for(i=Length;i>0;i--)
// {
// SingleNumber=Number/LCD_Pow(16,i-1)%16;
// if(SingleNumber<10)
// {
// LCD_WriteData(SingleNumber+'0');
// }
// else
// {
// LCD_WriteData(SingleNumber-10+'A');
// }
// }
//}///**
// * @brief 在LCD1602指定位置开始以二进制显示所给数字
// * @param Line 起始行位置,范围:1~2
// * @param Column 起始列位置,范围:1~16
// * @param Number 要显示的数字,范围:0~1111 1111 1111 1111
// * @param Length 要显示数字的长度,范围:1~16
// * @retval 无
// */
//void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
//{
// unsigned char i;
// LCD_SetCursor(Line,Column);
// for(i=Length;i>0;i--)
// {
// LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
// }
//}
lcd1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__//用户调用函数:
void LCD_Init();
void LCD_Clear();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
//void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
//void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
//void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
//void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);#endif
#ifndef _public_H
#define _public_H#include "REGX52.h" #define u16 unsigned int //对系统默认数据类型进行重定义
#define u8 unsigned char void Delay_us(u16 ten_us);
void Delay_ms(u16 ms);#endif
#include "public.h"/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void Delay_us(u16 ten_us)
{while(ten_us--);
}/*******************************************************************************
* 函 数 名 : delay_ms
* 函数功能 : ms延时函数,ms=1时,大约延时1ms
* 输 入 : ms:ms延时时间
* 输 出 : 无
*******************************************************************************/
void Delay_ms(u16 ms)
{u16 i,j;for(i=ms;i>0;i--)for(j=110;j>0;j--);
}