- 打卡等级:偶尔看看III
- 打卡总天数:31
- 最近打卡:2025-12-17 09:44:38
注册会员
- 积分
- 85
|
实验目的
掌握 HC595 串并转换的工作原理。
掌握单片机串并转换的编程方法。
先上电路
HC595 串并转换原理
串并转换的原理图如图 6-1 所示,本实验中使用 U21 一片 595 控制数码管的段
选。下面介绍一下 SN74HC595 芯片的使用。595(简称)包括一个八位串行输入、
并行输出的移位寄存器以及一个八位 D 型存储器。移位寄存器有一个串行移位输入
(SER)和一个串行输出(QH’)以及一个异步的低电平复位,存储寄存器有一个 8 位
并行的、具备三态的总线输出,当使能 OE 时(为低电平),存储寄存器的数据输出
到总线。这两个寄存器都有各自独立的时钟线控制。其真值表如图 6-2 所示,SRCLK
为移位寄存器的时钟线,上升沿时 SER 中的数据串入到移位寄存器中,RCLK 为存储
寄存器的时钟线,上升沿时将移位寄存器的数据锁存到存储寄存器中并从 8 位并行
输出端 QH-QA 输出。这样通过 3 个 IO 口对 595 的控制就可以间接控制数码管的 8
位段选端,节省了 IO 的开销。
了解了 SN74HC595 的使用方法,就可以编写简单的测试程序,串出一个 8 位的
数据。具体程序如下:
/*******************************************************
//函数名称:HC595_send_byte_r()
//函数功能:行方向上的HC595串出一个字节,从高位到低位串
//入口参数:dat:串入的字节
//返回参数:无
********************************************************/
void HC595_send_byte_r(uchar dat)
{
uchar i;
for (i=0;i<8;i++)
{
HC595_data_r = dat & 0x80; //从高位到低位串出
dat<<=1;
HC595_clk_r = 0; //HC595_clk_r上升沿将SER数据逐步传入到移位寄存器中
HC595_clk_r = 1;
}
}
实验测试
连线:用杜邦线将 JP27 P00-P07 连接 JP82 SL0-SL7
JP18 QH-QA 连接 JP81 DP-A
P10-P12 连接 JP16 SER3,SRCLK,RCLK
实验现象:数码管循环显示 0-F。
代码分析
/*******************************************************
* 程序功能:串并转换
* 接线说明:JP27 P00-P07 连接 JP82 SL0-SL7
* JP18 QH-QA 连接 JP81 DP-A
* P10-P12 连接 JP16 SER3, SRCLK, RCLK
* 实验现象:数码管循环显示0-F
* 日 期:2014/10/30
* 作 者:
*******************************************************/
#include "hal.h"
#include "display_io.h"
#define PosPort P0
// 全局变量定义
uchar dis_1ms_ok = 0; // 一列扫描时间到标志位
uchar dis_buff[8]; // 显示缓冲区(存储8位数码管的段码)
// 数码管字形表,供显示时查询(共阴数码管,0-F + 消隐)
uchar code disptable[17] = {
// 定义表格需加code,存储在程序存储区(ROM)
0x3F, // "0": 0B00111111
0x06, // "1": 0B00000110
0x5B, // "2": 0B01011011
0x4F, // "3": 0B01001111
0x66, // "4": 0B01100110
0x6D, // "5": 0B01101101
0x7D, // "6": 0B01111101
0x07, // "7": 0B00000111
0x7F, // "8": 0B01111111
0x6F, // "9": 0B01101111
0x77, // "A": 0B01110111
0x7C, // "b": 0B01111100
0x39, // "C": 0B00111001
0x5E, // "d": 0B01011110
0x79, // "E": 0B01111001
0x71, // "F": 0B01110001
0x00 // 全灭消隐
};
// 共阴数码管位选编码(低电平有效,依次选中第1-8位)
uchar code Position[] = {
0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F
};
/*******************************************************
* 函数名称:Timer0Init()
* 函数功能:定时器0初始化函数(1毫秒@12MHz)
* 入口参数:无
* 返回参数:无
********************************************************/
void Timer0Init(void)
{
TIMER_CLK_DIV(0, 12); // 设置定时器0时钟为12T模式
TIMER_TIME(0); // 设置定时器0为定时模式
TIMER_MODE(0, 0); // 设置定时器0工作模式0(13位定时器)
TL0 = 0x18; // 设置定时初值低8位
TH0 = 0xFC; // 设置定时初值高8位(12MHz下,1ms定时初值:FC18H)
CLR_TIMER_FLAG(0); // 清除定时器0溢出标志位TF0
}
/*******************************************************
* 函数名称:Timer0_ISR()
* 函数功能:定时器0中断服务程序(每1ms触发一次)
* 入口参数:无
* 返回参数:无
********************************************************/
void Timer0_ISR() interrupt T0_VECTOR using 1
{
dis_1ms_ok = 1; // 置位1ms扫描标志位(通知主循环执行显示)
}
/*******************************************************
* 函数名称:HC595_send_byte_r()
* 函数功能:向74HC595串行发送一个字节(从高位到低位)
* 入口参数:dat - 待发送的字节数据
* 返回参数:无
********************************************************/
void HC595_send_byte_r(uchar dat)
{
uchar i;
for (i = 0; i < 8; i++) {
HC595_data_r = dat & 0x80; // 取当前最高位(0x80=10000000B),输出到串行数据引脚
dat <<= 1; // 左移1位,准备发送下一位
HC595_clk_r = 0; // 拉低移位时钟(准备上升沿)
HC595_clk_r = 1; // 上升沿:将数据移入595移位寄存器
}
}
/*******************************************************
* 函数名称:display_io()
* 函数功能:数码管动态扫描显示(每1ms扫描1位,8位循环)
* 入口参数:无
* 返回参数:无
********************************************************/
void display_io(void)
{
static uchar dos = 0; // 静态变量:扫描位计数(0-7,对应8位数码管)
PosPort = 0xFF; // 位选端口全高,消隐(避免切换时的鬼影)
HC595_send_byte_r(dis_buff[dos]); // 串行发送当前位的段码(从显示缓冲区读取)
HC595_latch_r = 0; // 拉低锁存时钟(准备上升沿)
HC595_latch_r = 1; // 上升沿:将移位寄存器的数据锁存到存储寄存器(并行输出)
PosPort = Position[dos]; // 输出位选信号,选中当前数码管(低电平有效)
if (++dos >= 8) { // 扫描计数递增,完成8位后归零(循环扫描)
dos = 0;
}
}
/*******************************************************
* 函数名称:main()
* 函数功能:程序主函数(初始化+循环调度)
********************************************************/
void main(void)
{
uchar i, k = 0; // i:循环变量;k:字形表索引(0-16对应0-F+消隐)
uint msecond = 0; // 毫秒计数器(累计1秒)
// 初始化定时器0
Timer0Init();
TIMER_RUN(0, START); // 启动定时器0
TIMER_INT_EN(0, ON); // 使能定时器0中断
INT_GLOBAL_ENABLE(ON); // 开启全局中断
// 初始化显示缓冲区:所有数码管默认显示"0"
for (i = 0; i < 8; i++) {
dis_buff = disptable[0];
}
// 主循环(事件驱动:等待1ms扫描标志)
while (1) {
if (dis_1ms_ok) { // 检测到1ms扫描时间到
dis_1ms_ok = 0; // 清除标志位(避免重复处理)
display_io(); // 执行数码管扫描显示(更新当前位)
// 累计1秒后,更新显示缓冲区(循环切换显示内容)
if (++msecond >= 1000) {
msecond = 0; // 重置毫秒计数器
// 所有数码管显示同一字符(k对应的字形)
for (i = 0; i < 8; i++) {
dis_buff = disptable[k];
}
// 字形索引递增(0→1→...→15(F)→16(消隐)→0循环)
if (++k > 0x10) { // 0x10=16(消隐索引)
k = 0;
}
}
}
}
}
数码管底层扫描驱动
数码管的扫描底层驱动就是不考虑显示缓冲区是什么内容,当定时器 1ms 一到,
就将当前缓冲区中相应的段码显示出来。这种设计可以将底层驱动与上层程序控制
分开,更好的实现模块化,调试起来也比较方便。部分程序如下所示:
/*******************************************************
//函数名称:void display_io(void)
//函数功能:数码管1ms扫描一位
//入口参数:无
//返回参数:无
********************************************************/
void display_io(void)
{
static uchar dos; // 8位扫描计数
PosPort = 0xff; // 消隐
HC595_send_byte_r(dis_buff[dos]); // 串出段数据,段值
HC595_latch_r = 0; // HC595_latch_r上升沿将并出的数据锁存到存储寄存器中
HC595_latch_r = 1;
PosPort = Position[dos]; // 送位选
if (++dos >= 8) dos = 0; // 扫描完8位,回到第一位
}
程序相关宏文件:
// 全局中断控制:EA=(!!on),on非0则开总中断
#define INT_GLOBAL_ENABLE(on) EA=(!!on)
/********************************************************
// 功能:定时器运行操作控制
// 参数:num 计数器号(0-4) ;on 运行控制,1:启动,0停止
*********************************************************/
#define TIMER_RUN(num,on) \
st( \
if(num==0) \
TR0=on; \
else if(num==1) \
TR1=on; \
else if(num==2) \
(on)?(AUXR |=BIT4):(AUXR &= ~BIT4); \
else if(num==3) \
(on)? (T4T3M |= BIT3):(T4T3M &=~BIT3);\
else if(num==4) \
(on)? (T4T3M|= BIT4):(T4T3M &=~BIT4);\
)
/********************************************************
// 功能:定时器定时操作控制
// 参数:num 计数器号(0-4)
*********************************************************/
#define TIMER_TIME(num)\
st( \
if(num==0) \
TMOD |=~BIT2; \
else if(num==1) \
TMOD |=~BIT6; \
else if(num==2) \
AUXR |=~BIT3; \
else if(num==3) \
T4T3M |= ~BIT2; \
else if(num==4) \
T4T3M |= ~BIT6;)
/********************************************************
// 功能:定时器中断使能操作控制
// 参数:num 计数器号(0-4) ;on 使能控制,1:允许,0禁止 //1溢出中断允许
*********************************************************/
#define TIMER_INT_EN(num,on) \
if(num==0) \
ET0=on; \
else if(num==1) \
ET1=on; \
else if(num==2) \
(on) ? (IE2 |= BIT3) : (IE2 &= ~BIT3); \
else if(num==3) \
(on) ? (IE2 |= BIT5) : (IE2 &= ~BIT5); \
else if(num==4) \
(on) ? (IE2 |= BIT6) : (IE2&= ~BIT6);
/********************************************************
// 功能:定时器工作模式选择
// 参数:num 计数器号(0-1) ;sel模式选择(0-3)0:16位重载 1:16位计数器 2:8位重载 3:计数器无效
*********************************************************/
#define TIMER_MODE(num,sel) \
if(sel<4) \
{ \
if(num==0) \
{ \
TMOD &= ~0x03; \
TMOD |=sel; \
} \
else if(num==1) \
{ \
TMOD &= ~0x30; \
TMOD |=sel<<3; \
} \
}
/********************************************************
// 功能:定时器定时时间分频选择
// 参数:num 计数器号(0-4); sel分频选择; 1:1分频,0-12分频
*********************************************************/
#define TIMER_CLK_DIV(num,sel) \
st( \
if(num==0) \
(sel==1) ? (AUXR |= BIT7) :(AUXR &= ~BIT7); \
else if(num==1) \
(sel==1) ? (AUXR |= BIT6) :(AUXR &= ~BIT6); \
else if(num==2) \
(sel==1) ? (AUXR |= BIT2) :(AUXR &= ~BIT2); \
else if(num==3) \
(sel==1) ? (T4T3M |= BIT1) :(T4T3M &= ~BIT1); \
else if(num==4) \
(sel==1) ? (T4T3M |= BIT5) :(T4T3M &= ~BIT5);)
/*************************************
// 功能:清除定时器中断标志 //清除溢出中断
// 参数:num 计数器号(0-1)
*********************************************************/
#define CLR_TIMER_FLAG(num) \
st( \
if(num==0) \
TF0=0; \
else if(num==1) \
TF0=0; \
)
|
|