目录
步进电机实验
前言
基于普中单片机对51单片机进行学习
注:本篇笔记主要参考了普中单片机开发文档
独立按键实验
按键是一种电子开关,使用时按开关按钮,开关接通,当松开手时, 开关断开。
![](https://i-blog.csdnimg.cn/blog_migrate/a7e1afd6425a27b619054e9e86e1b955.png)
按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态, 如果按键按下,初始导通状态变为断开,初始断开状态变为导通。
由于机械点的弹性作用,按键开关在按下和松开时不会马上接通和断开,会有抖动。抖动时间一般为5ms到10ms,抖动会引起按键被误读多次,所以需要进行消抖处理。消抖处理有两种,软件消抖和硬件消抖。通常采用软件消抖。
软件消抖:
先读取按键的状态,如果得到按键按下之后,延时 10ms,再次
读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。
搭载在51单片机上的消抖方法
1,先设置 IO 口为高电平(由于开发板 IO 都有上拉电阻,所以默认 IO 为高电平)。
2,读取 IO 口电平确认是否有按键按下。
3,如有 IO 电平为低电平后,延时几个毫秒。
4,再读取该 IO 电平,如果仍然为低电平,说明按键按下。
5,执行按键控制程序。
独立按键电路构成是由各个按键的一个管脚连接在一起接地,按键其他引脚分别接到单片机 IO 口。检测按键使用的是IO口的输入功能。
硬件电路图
![](https://i-blog.csdnimg.cn/blog_migrate/b9a522a72da6a3cc2406a2cdb8174e67.png)
实现的功能:
通过开发板上的独立按键 K1 控制 D1 指示灯亮灭。
原理:通过key_scan扫描函数获取k1按键的状态,然后在主函数中通过if语句条件判断来翻转D1灯的电平达到亮灭的效果。
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;
//定义 LED1 控制脚
sbit LED1=P2^0;
//使用宏定义独立按键按下的键值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY_UNPRESS 0
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
u8 key_scan(u8 mode)
{
static u8 key=1;
if(mode)key=1;//连续扫描按键
if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
{
delay_10us(1000);//消抖
key=0;
if(KEY1==0)
return KEY1_PRESS;
else if(KEY2==0)
return KEY2_PRESS;
else if(KEY3==0)
return KEY3_PRESS;
else if(KEY4==0)
return KEY4_PRESS;
}
else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1) //无按键按下
{
key=1;
}
return KEY_UNPRESS;
}
void main()
{
u8 key=0;
while(1)
{
key=key_scan(0);
if(key==KEY1_PRESS)//检测按键 K1 是否按下
LED1=!LED1;//LED1 状态翻转
}
}
解析:
key_scan 函数带一个形参 mode,该参数用来设定是否连续扫描按键,如果mode 为 0,只能操作一次按键,只有当按键松开后才能触发下次的扫描,这样做的好处是可以防止按下一次出现多次触发的情况。如果 mode 为 1,函数是支持连续扫描的,即使按键未松开,在函数内部有if(mode==1)这条判断语句,因此 key 始终是等于 1 的,所以可以连续扫描按键,当按下某个按键,会一直返回这个按键的键值,这样做的好处是可以很方便实现连按操作。key_scan 函数还带有一个返回值,如果未有按键按下,返回值即为
KEY_UNPRESS,否则返回值即为对应按键的键值,如 KEY1_PRESS、KEY2_PRESS、 KEY3_PRESS、 KEY4_PRESS,这都是程序开头定义好的宏。 函数内定义了一个 static 变量 key,相当于全局变量。该函数按键的扫描是有优先级的,因为函数内用了 if...else if...else 格式。如果需要将其优先级设置一样,那么可以全部用 if 语句。
main 函数中主要就是调用 key_scan 函数用于检测按键,此时传入的 mode 值为 0,表示单次扫描按键,然后将扫描按键的值保存在变量 key 中,最后通过if 判断语句控制 LED1 状态。
矩阵按键实验
为减少按键占用过多的I/O资源,引入了矩阵按键。开发板上将 16 个按键排成 4 行 4 列,第一行将每个按键的一端连接在一起构成行线,第一列将每个按键的另一端连接在一起构成列线,这样便一共有 4 行 4 列共 8 根线,将这8根线连接到单片机的 8 个 I/O 口上,通过程序扫描键盘就可检测 16 个键。
按键检测是否被按下的原理:与该键对应的 I/O 口是否为低电平。
检测方法有两种:行列扫描法和线翻转法。
行列扫描法:
先送一列为低电平,其余几列全为高电平(确定了列数),然后立即轮流检测一次各行是否有低电平,若检测到某一行为低电平(确定了行数),则便可确认当前被按下的键是哪一行哪一列的,用同样方法轮流送各列一次低电平,再轮流检测一次各行是否变为低电平, 这样即可检测完所有的按键.
线翻转法,就是使所有行线为低电平时,检测所有列线是否有低电平,如果有,就记录列线值;然后再翻转,使所有列线都为低电平,检测所有行线的值,由于有按键按下,行线的值也会有变化,记录行线的值。从而就可以检测到全部按键。
硬件电路图
![](https://i-blog.csdnimg.cn/blog_migrate/5691ed26117fd7c4fc6f93fa7b7c9df2.png)
实现的功能:
通过数码管显示矩阵按键 S1-S16 按下后键值 0-F
行列扫描法实现
原理:for循环嵌套轮流将各列或各行输送低电平,后再将各行或各列检测是否有低电平从而判断是哪个按键被按下。
代码
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
#define KEY_MATRIX_PORT P1 //使用宏定义矩阵按键控制口
#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//共阴极数码管显示 0~F 的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
u8 key_matrix_ranks_scan(void)
{
u8 key_value=0;
KEY_MATRIX_PORT=0xf7;//给第一列赋值 0,其余全为 1
if(KEY_MATRIX_PORT!=0xf7)//判断第一列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第一列按键按下后的键值
{
case 0x77: key_value=1;break;
case 0xb7: key_value=5;break;
case 0xd7: key_value=9;break;
case 0xe7: key_value=13;break;
}
}
while(KEY_MATRIX_PORT!=0xf7);//等待按键松开
KEY_MATRIX_PORT=0xfb;//给第二列赋值 0,其余全为 1
if(KEY_MATRIX_PORT!=0xfb)//判断第二列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第二列按键按下后的键值
{
case 0x7b: key_value=2;break;
case 0xbb: key_value=6;break;
case 0xdb: key_value=10;break;
case 0xeb: key_value=14;break;
}
}
while(KEY_MATRIX_PORT!=0xfb);//等待按键松开
KEY_MATRIX_PORT=0xfd;//给第三列赋值 0,其余全为 1
if(KEY_MATRIX_PORT!=0xfd)//判断第三列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第三列按键按下后的键值
{
case 0x7d: key_value=3;break;
case 0xbd: key_value=7;break;
case 0xdd: key_value=11;break;
case 0xed: key_value=15;break;
}
}
while(KEY_MATRIX_PORT!=0xfd);//等待按键松开
KEY_MATRIX_PORT=0xfe;//给第四列赋值 0,其余全为 1
if(KEY_MATRIX_PORT!=0xfe)//判断第四列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第四列按键按下后的键值
{
case 0x7e: key_value=4;break;
case 0xbe: key_value=8;break;
case 0xde: key_value=12;break;
case 0xee: key_value=16;break;
}
}
while(KEY_MATRIX_PORT!=0xfe);//等待按键松开
return key_value;
}
void main()
{
u8 key=0;
while(1)
{
key=key_matrix_ranks_scan();
if(key!=0)
SMG_A_DP_PORT=gsmg_code[key-1];//得到的按键值减 1 换算成数组下标
对应 0-F 段码
}
}
解析:通过key_matrix_ranks_scan按键扫描函数利用行列式扫描原理用for循环判断哪个按键被按下并返回,在主函数中通过判断按键的值来将按键按下的值换算成数码管数组的下标从而显示0~F。
线翻转法实现
原理:两次if和switch语句,先找到列或行低电平后,翻转列或行再测量低电平判断是哪个按键被按下。
代码
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
#define KEY_MATRIX_PORT P1 //使用宏定义矩阵按键控制口
#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//共阴极数码管显示 0~F 的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
u8 key_matrix_flip_scan(void)
{
static u8 key_value=0;
KEY_MATRIX_PORT=0x0f;//给所有行赋值 0,列全为 1
if(KEY_MATRIX_PORT!=0x0f)//判断按键是否按下
{
delay_10us(1000);//消抖
if(KEY_MATRIX_PORT!=0x0f)
{
//测试列
KEY_MATRIX_PORT=0x0f;
switch(KEY_MATRIX_PORT)//保存行为 0,按键按下后的列值
{
case 0x07: key_value=1;break;
case 0x0b: key_value=2;break;
case 0x0d: key_value=3;break;
case 0x0e: key_value=4;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=0;
return key_value;
}
void main()
{
u8 key=0;
while(1)
{
key=key_matrix_ranks_scan();
if(key!=0)
SMG_A_DP_PORT=gsmg_code[key-1];//得到的按键值减 1 换算成数组下标
对应 0-F 段码
}
}
解析:通过key_matrix_flip_scan函数利用线翻转式原理设置两次if switch进行扫描获取返回值,在主函数中通过判断按键的值来将按键按下的值换算成数码管数组的下标从而显示0~F。
IO 扩展(串转并)-74HC595实验
74HC595 是一个 8 位串行输入、并行输出的位移缓存器,其中并行输出为三态输出(即高电平、低电平和高阻抗)。
![](https://i-blog.csdnimg.cn/blog_migrate/000c0f57b66c360bd6828dc074321efa.png)
注:
不同芯片引脚名称会有差别,看引脚序号即可。
15 和 1 到 7 脚 QA--QH:并行数据输出
9 脚 QH 非:串行数据输出
10 脚 SCLK 非( MR) : 低电平复位引脚
11 脚 SCK( SHCP) : 移位寄存器时钟输入
12 脚 RCK( STCP) : 存储寄存器时钟输入
13 脚 G 非( OE) : 输出有效
14 脚 SER( DS) : 串行数据输入
移位寄存器和存储器是单独的时钟。数据在 SCK 的上升沿输入,在 RCK 的上升沿进入到存储器中。移位寄存器有一个串行输入(DS),和一个串行输出(Q7 非),和一个异步的低电平复位,存储寄存器有一个并行 8 位的,具有三态的总线输出,当 MR 为高电平,OE 为低电平时,数据在 SHCP 上升沿进入移位寄存器,在 STCP 上升沿输出到并行端口。
硬件电路图
![](https://i-blog.csdnimg.cn/blog_migrate/07e62bf562c439e73ac82bdf349f54a3.png)
74HC595 需要用到的控制管脚 SER、RCLK、SRCLK 直接连接到 51 单片机的 P3.4-P3.6 IO 口上,输出端则是直接连接到 LED 点阵模块的行端口上,即为 LED 发光二极管的阳极,LED 点阵的列则为发光二极管的阴极。
实现功能:通过 74HC595 模块控制 LED 点阵以一行循环滚动显示
原理:编写74HC595的控制函数写入传输数据,从而控制led点阵的显示。
代码
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义 74HC595 控制管脚
sbit SRCLK=P3^6; //移位寄存器时钟输入
sbit RCLK=P3^5; //存储寄存器时钟输入
sbit SER=P3^4; //串行数据输入
#define LEDDZ_COL_PORT P0 //点阵列控制端口
u8 ghc595_buf[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
void hc595_write_data(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)//循环 8 次即可将一个字节写入寄存器中
{
SER=dat>>7;//优先传输一个字节中的高位
dat<<=1;//将低位移动到高位
SRCLK=0;
delay_10us(1);
SRCLK=1;
delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中
}
RCLK=0;
delay_10us(1);
RCLK=1;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出
}
void main()
{
u8 i=0;
LEDDZ_COL_PORT=0x00;//将 LED 点阵列全部设置为 0,即 LED 阴极为低电平
while(1)
{
for(i=0;i<8;i++)
{
hc595_write_data(0x00);//消除前面寄存器缓存数据
hc595_write_data(ghc595_buf[i]);//写入新的数据
delay_ms(500);//延时 500ms
}
}
}
解析:
首先定义好 74HC595 控制管脚,以及点阵列控制口,代码中重新定义了一个 ms 级延时函数 delay_ms,该函数与前面 delay_10us 类似,都是利用循环占用 CPU 起到延时效果。然后又定义了 74HC595 的控制函数 hc595_write_data,该函数完全按照 74HC595 的通信时序要求编写,主要要注意的是 74HC595 是先传输字节的高位后传输低位,所以需要将字节低位移动到高位
传输,在传输数据时,要注意移位寄存器时钟和存储寄存器时钟的先后顺序,将 要写入的数据先传输到 74HC595 寄存器中,即在准备好每位数据时要将 SRCLK 进行一个上升沿变化,此时即可将数据传输到寄存器内,待循环 8 次即一个字节传输到寄存器中时,就可以来一个存储时钟上升沿,此时就可以将 74HC595 寄存器中的数据全部一次传输到 595 端口输出。最后就是在 main 函数中调用 74HC595的控制函数,将实验中要实现的效果数据写入进去,从而控制 LED 点阵的阳极,而阴极由 P0 口控制,默认初始化时已经设置为 0,也就是说只要 595 输出高电 平,那么对应的行就会点亮。
注意:做 LED 点阵实验时,要将 LED 点阵旁的 J24 黄色跳线帽短接到 GND一端。
LED 点阵实验
LED 点阵是由发光二极管排列组成的显示器件。
![](https://i-blog.csdnimg.cn/blog_migrate/0af7405363d7736a65e2056300a26a84.png)
8*8 点阵共由 64 个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上,当对应的某一行置 1 电平,某一列置 0 电平,则相应的二极管就亮;如要将第一个点点亮,则 1 脚接高电平 a 脚接低电平,则第一个点就亮了;如果要将第一行点亮,则第 1 脚要接高电平,而(a、b、c、d、e、f、g、h )这些引脚接低电平,那么第一行就会点亮;如要将第一列点亮,则第 a 脚接低电平, 而(1、2、3、4、5、6、7、8)接高电平,那么第一列就会点亮。
要同时点亮多个LED灯,用到动态数码管的动态扫描原理,需要某行或某列有效,同时使多列或多行有效。
在第一行亮灯一段时间以后灭掉,点亮第二行一段时间以后灭掉,点亮第三行一段时间以后灭掉,如此点亮,直到八行全部点亮一次,在第一行点亮到最后一行灭掉的总时间不能超过人肉眼可识别的时间,即 24 毫秒
硬件电路图
![](https://i-blog.csdnimg.cn/blog_migrate/41e5f5d928614b3afbc7062fd4db1503.png)
注意:做 LED 点阵实验时,一定要将 LED 点阵旁的 J24 黄色跳线帽短接到 GND一端。
当不使用 LED 点阵时,将 LED 点阵旁的 J24 黄色跳线帽短接到 VCC一
端。
LED点阵点亮一个点
实现功能:
在点阵屏上点亮一个点
原理:LED点阵某列输入低电平,其余输入高电平,某行输入高电平,其余行输入低电平即可
代码
#include "reg51.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义74HC595控制管脚
sbit SRCLK=P3^6; //移位寄存器时钟输入
sbit RCLK=P3^5; //存储寄存器时钟输入
sbit SER=P3^4; //串行数据输入
#define LEDDZ_COL_PORT P0 //点阵列控制端口
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void hc595_write_data(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)//循环8次即可将一个字节写入寄存器中
{
SER=dat>>7;//优先传输一个字节中的高位
dat<<=1;//将低位移动到高位
SRCLK=0;
delay_10us(1);
SRCLK=1;
delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中
}
RCLK=0;
delay_10us(1);
RCLK=1;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出
}
void main()
{
u8 i=0;
LEDDZ_COL_PORT=0x7f;//将LED点阵左边第一列设置为0,即LED阴极为低电平,其余列为1,即高电平
while(1)
{
hc595_write_data(0x80);//将LED点阵上边第一行设置为1,即LED阳极为高电平,其余行为0,即低电平
}
}
解析:
将第一个点对应的行为高电平,列为低电平即可。也就是让 74HC595 输出 0X80(1000 0000),这样点阵第一行就是高电平, 而 P0 口输出 0X7F(0111 1111),这样点阵第一列就是低电平,从而让 LED 点阵第一个点点亮。
LED点阵点亮数字
实现功能:在点阵屏上点亮一个数字
原理:利用动态扫描原理,快速对各行各列的对应点输出高低电平从而点亮数字。
代码
#include "reg51.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义74HC595控制管脚
sbit SRCLK=P3^6; //移位寄存器时钟输入
sbit RCLK=P3^5; //存储寄存器时钟输入
sbit SER=P3^4; //串行数据输入
#define LEDDZ_COL_PORT P0 //点阵列控制端口
//u8 gled_row[8]={0x00,0x7C,0x82,0x82,0x82,0x7C,0x00,0x00};//LED点阵显示数字0的行数据
u8 gled_row1[8]={0x1E,0x3F,0x7F,0xFE,0xFE,0x7F,0x3F,0x1E};
//u8 gled_row2[8]={0x00,0x1C,0x14,0xFF,0x14,0x1C,0x00,0x00};
u8 gled_col[8]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};//LED点阵显示数字0的列数据
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void hc595_write_data(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)//循环8次即可将一个字节写入寄存器中
{
SER=dat>>7;//优先传输一个字节中的高位
dat<<=1;//将低位移动到高位
SRCLK=0;
delay_10us(1);
SRCLK=1;
delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中
}
RCLK=0;
delay_10us(1);
RCLK=1;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出
}
void main()
{
u8 i=0;
while(1)
{
for(i=0;i<8;i++)//循环8次扫描8行、列
{
LEDDZ_COL_PORT=gled_col[i];//传送列选数据
hc595_write_data(gled_row1[i]);//传送行选数据
delay_10us(100000);//延时一段时间,等待显示稳定
hc595_write_data(0x00);//消影
}
}
}
解析:
main 函数中主要是 在 while 循环内从上至下,从左至右不断扫描 8 行、列,即首先设置左边第一列有效(P0.7 输出低电平),其余列无效(P0.6-P0.0 输出高电平),然后通过74HC595 输出该列对应的行数据,延时一段时间等待显示稳定,最后清除列对应的行数据,即消影。
LED点阵点亮图像
实现的功能:LED点阵点亮图像。
原理:同LED点阵点亮数字原理相同。相同
代码
#include "reg51.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义74HC595控制管脚
sbit SRCLK=P3^6; //移位寄存器时钟输入
sbit RCLK=P3^5; //存储寄存器时钟输入
sbit SER=P3^4; //串行数据输入
#define LEDDZ_COL_PORT P0 //点阵列控制端口
u8 gled_row[8]={0x38,0x7C,0x7E,0x3F,0x3F,0x7E,0x7C,0x38};//LED点阵显示图像的行数据
u8 gled_col[8]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};//LED点阵显示图像的列数据
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void hc595_write_data(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)//循环8次即可将一个字节写入寄存器中
{
SER=dat>>7;//优先传输一个字节中的高位
dat<<=1;//将低位移动到高位
SRCLK=0;
delay_10us(1);
SRCLK=1;
delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中
}
RCLK=0;
delay_10us(1);
RCLK=1;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出
}
void main()
{
u8 i=0;
while(1)
{
for(i=0;i<8;i++)//循环8次扫描8行、列
{
LEDDZ_COL_PORT=gled_col[i];//传送列选数据
hc595_write_data(gled_row[i]);//传送行选数据
delay_10us(100);//延时一段时间,等待显示稳定
hc595_write_data(0x00);//消影
}
}
}
解析
与点亮数字类似,用了数组来存储各行的点的高低电平输出,在main 函数中主要是 在 while 循环内从上至下,从左至右不断扫描 8 行、列,即首先设置左边第一列有效(P0.7 输出低电平),其余列无效(P0.6-P0.0 输出高电平),然后通过74HC595 输出该列对应的行数据,延时一段时间等待显示稳定,最后清除列对应的行数据,即消影。
直流电机实验
直流电机是指能将直流电能转换成机械能(直流电动机)或将机械能转换成直流电能(直流发电机)的旋转电机。它是能实现直流电能和机械能互相转换的电机。
由定子和转子两大部分组成,定子的主要作用是产生磁场,转子的主要作用是产生电磁转矩和感应电动势
直流电机没有正负之分,在两端加上直流电就能工作。需要知道直流电机的额定电压和额定功率,不能使之长时间超负荷运作
ULN2003
芯片介绍
一个单片高电压、高电流的达林顿晶体管阵列集成电路
可以用来驱动直流电机和五线四相步进电机(28BYJ-48 步进电机)。
![](https://i-blog.csdnimg.cn/blog_migrate/e05c7d49ca0fb9c776e373f9472d396a.png)
其内部
相当于非门电路,即输入高输出为低,输入为低输出是高
使用该芯片驱动直流电机,只可实现单方向控制,电机一端接电源正极,另一端接芯片的输出口。
控制五线四相步进电机,则可将四路输出接到步进电机的四相上,电机另一条线接电源正。
硬件电路图
![](https://i-blog.csdnimg.cn/blog_migrate/cf7bdf42d4fdb6cc110a88adc6127f2d.png)
ULN2003 的输入口与单片机的 P1.0-P1.3 连接,对应输出则是OUT1-OUT4,而 J47 则是提供给外部连接电机的接口,可以支持直流电机、五线四相步进电机 28BYJ-48 连接。
实现的功能:
直流电机工作约 5S 后停止。
原理: 控制P10引脚的高低电平输入使电机工作
代码
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义直流电机控制管脚
sbit DC_Motor=P1^0;
#define DC_MOTOR_RUN_TIME 5000 //定义直流电机运行时间为5000ms
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
DC_Motor=1;//开启电机
delay_ms(DC_MOTOR_RUN_TIME);
DC_Motor=0;//关闭电机
while(1)
{
}
}
解析
main函数中 先控制P1引脚输出高电平使电机转动,延时5s后,再控制P1引脚输出低电平使电机停止转动。
注意:直流电机的两根线要连接在 J47 端子的 01 和 5V 上
步进电机实验
步进电机是将电脉冲信号转变为角位移或线位移的开环控制元件
在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受
负载变化的影响,即给电机加一个脉冲信号,电机则转过一个步距角。
工作原理
当电流流过定子绕组时,定子绕组产生一矢量磁场。磁场会带动转子旋转一定的角度,使得转子的一对磁场方向与定子的磁场方向一致。当定子的矢量磁场旋转一个角度。转子也随着该磁场转步距角。每输入一个电脉冲,电动机转动一个角度前进一步。它输出的角位移与输入的脉冲数成正比,转速与脉冲频率成正比。改变绕组通电的顺序,电机就会反转。
可以控制脉冲数量、频率及电动机各相绕组的通电顺序来控制步进电机的转动。
![](https://i-blog.csdnimg.cn/blog_migrate/d5a24b444a4bfcaeaa36e2d601e41b1f.png)
步进电机又分为单极性的步进电机和双极性的步进电机。单极性步进电机使用的是单极性绕组。双极性步进电机使用的是双极性绕组。利用绕组简单地改变电流的方向,就能改变该组的极性。
双极性步进电机驱动原理
![](https://i-blog.csdnimg.cn/blog_migrate/d211ba6242edb1ac6db3ac466a2431ad.png)
1. A 相通电,B 相不通电
2. A、B 相全部通电,且电流相同,产生相同磁性
3. B 相通电,A 断电
4. B 相通电,A 相通电,且电流相等,产生相同磁性
5. A 相通电,B 断电
6. A、B 相全部通电,且电流相同,产生相同磁性
7. B 相通电,A 断电
8. B 相通电,A 相通电,且电流相等,产生相同磁性
其中 1~4 步与 5~8 步的电流方向相反(电流相反,电磁的极性就相反)这样就产生了顺时针旋转,同理逆时针是将通电顺序反过来即可。
单极性步进电机驱动原理
![](https://i-blog.csdnimg.cn/blog_migrate/121a0a194482b02121f4e12fa536b8e7.png)
单极性步进电机无法通过改变电流方向来改变没相的磁场方向
通电顺序 A->AB->B->BC->C->CD->D->DA
转子每次只走半步 45 度,也被称为半步驱动,与整步相比半步的旋转方式旋转起来更加的顺滑
28BYJ-48
步进电机
![](https://i-blog.csdnimg.cn/blog_migrate/8f1ab6f6a3a15ddab752d829f48c11f6.png)
减速比:1:64,步进角为 5.625/64 度,如果需要转动一圈,那么需要 360/5.625*64=4096 个脉冲信号
实现的功能:当按下 KEY1 键可调节电机旋转方向;当按下 KEY2 键,电机加速; 当按下 KEY3 键,电机减速。
原理:通过对步进电机编写控制函数和按键控制函数达到所需的要求。
代码
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义ULN2003控制步进电机管脚
sbit IN1_A=P1^0;
sbit IN2_B=P1^1;
sbit IN3_C=P1^2;
sbit IN4_D=P1^3;
//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;
//使用宏定义独立按键按下的键值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY_UNPRESS 0
// 定义步进电机速度,值越小,速度越快
// 最小不能小于1
#define STEPMOTOR_MAXSPEED 1
#define STEPMOTOR_MINSPEED 5
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
void step_motor_28BYJ48_send_pulse(u8 step,u8 dir)
{
u8 temp=step;
if(dir==0) //如果为逆时针旋转
temp=7-step;//调换节拍信号
switch(temp)//8个节拍控制:A->AB->B->BC->C->CD->D->DA
{
case 0: IN1_A=1;IN2_B=0;IN3_C=0;IN4_D=0;break;
case 1: IN1_A=1;IN2_B=1;IN3_C=0;IN4_D=0;break;
case 2: IN1_A=0;IN2_B=1;IN3_C=0;IN4_D=0;break;
case 3: IN1_A=0;IN2_B=1;IN3_C=1;IN4_D=0;break;
case 4: IN1_A=0;IN2_B=0;IN3_C=1;IN4_D=0;break;
case 5: IN1_A=0;IN2_B=0;IN3_C=1;IN4_D=1;break;
case 6: IN1_A=0;IN2_B=0;IN3_C=0;IN4_D=1;break;
case 7: IN1_A=1;IN2_B=0;IN3_C=0;IN4_D=1;break;
default: IN1_A=0;IN2_B=0;IN3_C=0;IN4_D=0;break;//停止相序
}
}
u8 key_scan(u8 mode)
{
static u8 key=1;
if(mode)key=1;//连续扫描按键
if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
{
delay_10us(1000);//消抖
key=0;
if(KEY1==0)
return KEY1_PRESS;
else if(KEY2==0)
return KEY2_PRESS;
else if(KEY3==0)
return KEY3_PRESS;
else if(KEY4==0)
return KEY4_PRESS;
}
else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1) //无按键按下
{
key=1;
}
return KEY_UNPRESS;
}
void main()
{
u8 key=0;
u8 dir=0;//默认逆时针方向
u8 speed=STEPMOTOR_MAXSPEED;//默认最大速度旋转
u8 step=0;
while(1)
{
key=key_scan(0);
if(key==KEY1_PRESS)//换向
{
dir=!dir;
}
else if(key==KEY2_PRESS)//加速
{
if(speed>STEPMOTOR_MAXSPEED)
speed-=1;
}
else if(key==KEY3_PRESS)//减速
{
if(speed<STEPMOTOR_MINSPEED)
speed+=1;
}
step_motor_28BYJ48_send_pulse(step++,dir);
if(step==8)step=0;
delay_ms(speed);
}
}
解析
step_motor_28BYJ48_send_pulse 函数用于输出一个数据给 ULN2003,从而实现向步进电机发送一个脉冲信号。它有两个形参,第一个为 step,指定步进序号,可选值为 0-7,代表步进电机控制信号的 8 个节拍。第二个为 dir,指定电机的旋转方向,可选 1:顺时针,0:逆时针。这里指的顺时针逆时针是两个不同方向。函数先判断 dir 参数值,如果为 0,则为逆时针旋转,将 step 值变为 8 的互补数,其实就是将 8 个节拍反向输出。 主函数实现的功能很简单,首先定义一些变量,进入循环,检测是否有按键按下,当 KEY1 按下,切换步进电机方向;当 KEY2 按下,使电机加速;当 KEY3按下,使电机减速。 程序中所用 STEPMOTOR_MAXSPEED 和STEPMOTOR_MINSPEED 在开头已定义,表示步进电机最大运行速度和最低运行速度,
总结
对单片机按键,led点阵,I/O口扩展,电机等进行了学习。