一文搞懂74HC595芯片(附详细使用方法)
一、74HC595芯片概述
1.1 什么是74HC595?
74HC595是一款高速CMOS逻辑器件,采用8位串行输入/并行输出移位寄存器设计。该芯片具有存储寄存器和三态输出功能,能够实现串行数据到并行数据的转换。由于其高效的数据传输能力和灵活的控制方式,74HC595在LED点阵屏、数码管显示、IO扩展等嵌入式系统领域得到了广泛应用。
1.2 主要特性
- 8位串行输入、并行输出
- 工作电压范围:2.0V至6.0V(典型5V)
- 高电流输出:每个输出引脚可提供最大35mA电流
- 低功耗:静态电流低至几微安
- 高速操作:时钟频率最高可达100MHz
- 级联能力:支持多芯片级联扩展
- 三态输出:具有输出使能控制
- 存储寄存器:数据可保持直至更新
1.3 应用场景
- LED显示屏控制
- 数码管驱动
- IO端口扩展
- 继电器阵列控制
- 按键矩阵扫描
- 步进电机控制
- 数据采集系统
二、74HC595脚位图及详细说明
2.1 芯片引脚图
┌──────┬──────┐
Q1 ─┤1 │ 16├─ VCC
Q2 ─┤2 15├─ Q0
Q3 ─┤3 14├─ DS (SER)
Q4 ─┤4 74HC59513├─ OE (低电平使能输出)
Q5 ─┤5 12├─ ST_CP (RCK)
Q6 ─┤6 11├─ SH_CP (SCK)
Q7 ─┤7 10├─ MR (低电平复位)
GND ─┤8 9├─ Q7'
└──────┴──────┘
2.2 引脚功能详解
2.2.1 电源引脚
- VCC(引脚16):电源正极,工作电压2.0-6.0V,典型5V
- GND(引脚8):电源地
2.2.2 数据输入引脚
- DS/SER(引脚14) :串行数据输入引脚
- 每次输入1位数据(0或1)
- 数据在时钟上升沿被采样
- 级联时连接到前一级的Q7'
2.2.3 时钟控制引脚
-
SH_CP/SCK(引脚11):移位寄存器时钟输入
- 上升沿有效:数据从DS引脚移入移位寄存器
- 已存在的数据依次向后移位
- 最高频率可达100MHz
-
ST_CP/RCK(引脚12):存储寄存器时钟输入
- 上升沿有效:移位寄存器数据转移到存储寄存器
- 数据锁存到输出端
- 可实现同步更新多个595芯片
2.2.4 控制引脚
-
OE(引脚13):输出使能控制(低电平有效)
- 低电平:输出使能,Q0-Q7输出数据
- 高电平:输出高阻态,可防止总线冲突
- 可用于PWM调光控制
-
MR(引脚10):主复位(低电平有效)
- 低电平:清空移位寄存器(不影响存储寄存器)
- 通常接VCC保持高电平
2.2.5 输出引脚
-
Q0-Q7(引脚15,1-7):并行数据输出
- 8位并行输出,驱动能力强
- 可驱动LED、继电器等负载
- 输出电流:±35mA
-
Q7'(引脚9):串行数据输出
- 用于芯片级联
- 输出移位寄存器的第9位(溢出位)
- 连接到下一级595的DS引脚
2.3 电气特性参数
| 参数 | 符号 | 最小值 | 典型值 | 最大值 | 单位 | 条件 |
|---|---|---|---|---|---|---|
| 工作电压 | VCC | 2.0 | 5.0 | 6.0 | V | - |
| 输入高电平 | VIH | 3.15 | - | - | V | VCC=5V |
| 输入低电平 | VIL | - | - | 1.35 | V | VCC=5V |
| 输出高电平 | VOH | 4.4 | - | - | V | IOH=-6mA |
| 输出低电平 | VOL | - | - | 0.33 | V | IOL=6mA |
| 输出电流 | IOH/IOL | - | - | ±35 | mA | - |
| 静态电流 | ICC | - | 8 | 80 | μA | - |
| 工作频率 | fmax | - | - | 100 | MHz | - |
2.4 使用注意事项
- 上电初始化:上电时移位寄存器状态不确定,建议先发送复位脉冲
- 未用引脚处理:不用的输入引脚应连接到VCC或GND
- 电源去耦:VCC和GND之间应加0.1μF陶瓷电容
- 输出保护:驱动感性负载时应加续流二极管
- 散热考虑:多路输出全高或全低时,注意芯片功耗
三、74HC595内部结构和工作原理
3.1 内部结构框图
74HC595内部包含三个主要部分:
-
8位串行输入移位寄存器
-
8位存储寄存器(锁存器)
-
8位三态输出缓冲器
┌─────────────────────────────────────────┐
│ 74HC595 │
│ DS ────┬──→ 移位寄存器 ───┬──→ 存储寄存器 ───→ 输出缓冲器 ───→ Q0-Q7 │
│ │ 8位 │ 8位 │ 三态控制 │
│ │ │ │ │
│ SCK ───┘ │ │ │
│ │ │ │
│ RCK ────────────────────┘ │ │
│ │ │
│ OE ──────────────────────────────────────┘ │
│ │
│ MR ───────────────→ 复位控制逻辑 │
│ │
│ Q7' ←────────────────────────────────────────────────────┘
└────────────────────────────────────────────────────────────┘
3.2 移位寄存器工作原理
3.2.1 数据移位过程
移位寄存器类似于一个8位的串行输入、串行输出的FIFO队列。数据从DS引脚输入,在SCK时钟上升沿被采样并移入:
c
// 模拟移位寄存器工作过程
uint8_t shift_register = 0x00; // 8位移位寄存器
// 向移位寄存器输入1位数据
void shift_bit(uint8_t bit_data) {
// 所有位向左移动一位
shift_register <<= 1;
// 新数据放入最低位
shift_register |= (bit_data & 0x01);
}
// 连续输入8位数据
void shift_byte(uint8_t byte_data) {
for(int i = 0; i < 8; i++) {
// 从最高位开始输入
uint8_t bit = (byte_data >> (7 - i)) & 0x01;
shift_bit(bit);
// 产生时钟上升沿(实际硬件中)
// pulse(SCK);
}
}
3.2.2 时序图分析
DS: __█████████___________________________________________
|数据位D7|D6|D5|D4|D3|D2|D1|D0|
SCK: _|▔▔|___|▔▔|___|▔▔|___|▔▔|___|▔▔|___|▔▔|___|▔▔|___|▔▔|_
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
时钟上升沿采样数据
移位寄存器变化:
初始: [?, ?, ?, ?, ?, ?, ?, ?]
D7后: [?, ?, ?, ?, ?, ?, ?, D7]
D6后: [?, ?, ?, ?, ?, ?, D7, D6]
...
D0后: [D7, D6, D5, D4, D3, D2, D1, D0]
3.2.3 移位寄存器状态转换表
| SCK时钟 | DS输入 | 移位寄存器状态变化 |
|---|---|---|
| 上升沿0 | D7 | [X,X,X,X,X,X,X] → [X,X,X,X,X,X,D7] |
| 上升沿1 | D6 | [X,X,X,X,X,X,D7] → [X,X,X,X,X,D7,D6] |
| 上升沿2 | D5 | [X,X,X,X,X,D7,D6] → [X,X,X,X,D7,D6,D5] |
| 上升沿3 | D4 | [X,X,X,X,D7,D6,D5] → [X,X,X,D7,D6,D5,D4] |
| 上升沿4 | D3 | [X,X,X,D7,D6,D5,D4] → [X,X,D7,D6,D5,D4,D3] |
| 上升沿5 | D2 | [X,X,D7,D6,D5,D4,D3] → [X,D7,D6,D5,D4,D3,D2] |
| 上升沿6 | D1 | [X,D7,D6,D5,D4,D3,D2] → [D7,D6,D5,D4,D3,D2,D1] |
| 上升沿7 | D0 | [D7,D6,D5,D4,D3,D2,D1] → [D6,D5,D4,D3,D2,D1,D0] |
注意:第一个输入的数据最终会出现在Q7(最高位),最后输入的数据出现在Q0(最低位)。
3.3 存储寄存器工作原理
存储寄存器是一个8位D触发器,用于锁存移位寄存器中的数据:
c
// 模拟存储寄存器工作过程
uint8_t storage_register = 0x00; // 8位存储寄存器
// 将移位寄存器数据转移到存储寄存器
void latch_data(void) {
storage_register = shift_register; // 数据转移
// 实际硬件中在RCK上升沿执行
}
// 输出数据到引脚
void output_data(void) {
// 如果OE为低电平,输出数据
if(OE == LOW) {
Q0 = (storage_register >> 0) & 0x01;
Q1 = (storage_register >> 1) & 0x01;
Q2 = (storage_register >> 2) & 0x01;
Q3 = (storage_register >> 3) & 0x01;
Q4 = (storage_register >> 4) & 0x01;
Q5 = (storage_register >> 5) & 0x01;
Q6 = (storage_register >> 6) & 0x01;
Q7 = (storage_register >> 7) & 0x01;
} else {
// OE为高电平,输出高阻态
Q0 = Q1 = Q2 = Q3 = Q4 = Q5 = Q6 = Q7 = HIGH_Z;
}
}
3.4 输出使能控制
OE引脚控制输出缓冲器的三态门:
- OE = 0:输出使能,Q0-Q7输出存储寄存器的数据
- OE = 1:输出高阻态,Q0-Q7与内部电路断开
这个特性非常有用:
- 总线共享:多个595输出可以连接到同一总线
- PWM调光:通过控制OE实现LED亮度调节
- 省电模式:不需要输出时关闭输出
c
// PWM调光示例
void led_pwm(uint8_t brightness) {
// 设置LED全亮
send_data_to_595(0xFF);
latch_data();
// PWM控制亮度
for(int i = 0; i < 255; i++) {
if(i < brightness) {
OE = 0; // 输出使能
} else {
OE = 1; // 输出关闭
}
delay_us(10); // PWM周期控制
}
}
3.5 复位功能
MR(主复位)引脚用于清零移位寄存器:
- MR = 0:移位寄存器清零(存储寄存器不受影响)
- MR = 1:正常工作
c
// 复位移位寄存器
void reset_shift_register(void) {
MR = 0; // 开始复位
delay_us(1); // 保持至少几纳秒
MR = 1; // 结束复位
// 移位寄存器已清零,但输出保持不变
}
四、74HC595级联使用
4.1 级联原理
74HC595支持多芯片级联,形成更长的移位寄存器链:
第一级595 第二级595 第N级595
┌─────────┐ Q7' ┌─────────┐ Q7' ┌─────────┐
│ ├──────────────→│ DS │ │ │
│ DS ←──┤ │ ├─────────────→│ DS │
│ │ │ │ │ │
│ SCK ←──┼──────────────→│ SCK ├─────────────→│ SCK │
│ │ │ │ │ │
│ RCK ←──┼──────────────→│ RCK ├─────────────→│ RCK │
│ │ │ │ │ │
│ Q0-Q7 │ │ Q0-Q7 │ │ Q0-Q7 │
└─────────┘ └─────────┘ └─────────┘
4.2 级联数据传输过程
假设级联2个595,需要发送16位数据:
c
// 向2个级联的595发送16位数据
void send_16bit_data(uint16_t data) {
// 从最高位开始发送(第2个595的数据先发送)
for(int i = 15; i >= 0; i--) {
// 取出当前位
uint8_t bit = (data >> i) & 0x01;
// 设置数据线
DS = bit;
// 产生SCK上升沿
SCK = 0;
delay_us(1);
SCK = 1;
delay_us(1);
SCK = 0;
}
// 所有数据发送完毕后,一次性锁存到输出
RCK = 0;
delay_us(1);
RCK = 1; // 上升沿锁存
delay_us(1);
RCK = 0;
}
4.3 级联时序分析
级联2个595,发送数据0x1234(二进制:0001 0010 0011 0100)
发送顺序:
1. 先发送第二个595的数据:0x12
2. 再发送第一个595的数据:0x34
时序:
DS: [0x12的D7]...[0x12的D0][0x34的D7]...[0x34的D0]
SCK: 8个脉冲 8个脉冲
RCK: 在16个SCK脉冲后产生一个上升沿
最终输出:
第一个595 Q0-Q7: 0x34 (00110100)
第二个595 Q0-Q7: 0x12 (00010010)
4.4 级联编程示例(3片595控制24个LED)
c
#include <reg51.h>
#include <intrins.h>
// 引脚定义
sbit DS = P3^4; // 串行数据输入
sbit SCK = P3^6; // 移位时钟
sbit RCK = P3^5; // 存储时钟
sbit OE = P3^7; // 输出使能(可选)
// 延时函数
void delay_us(unsigned int us) {
while(us--) {
_nop_(); _nop_(); _nop_(); _nop_();
}
}
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 114; j++);
}
// 向级联的595发送24位数据
void send_24bit_data(unsigned long data) {
unsigned char i;
// 从最高位开始发送(第3片595的数据先发送)
for(i = 24; i > 0; i--) {
// 取出当前位(i-1是因为从23到0)
if(data & ((unsigned long)1 << (i - 1))) {
DS = 1;
} else {
DS = 0;
}
// 产生SCK上升沿
SCK = 0;
delay_us(1);
SCK = 1;
delay_us(1);
SCK = 0;
}
// 产生RCK上升沿,锁存数据到输出
RCK = 0;
delay_us(1);
RCK = 1;
delay_us(1);
RCK = 0;
}
// LED流水灯效果
void led_flow_effect(void) {
unsigned long pattern = 0x000001; // 初始只有一个LED亮
unsigned char direction = 0; // 0:向右移动,1:向左移动
while(1) {
// 发送数据到595
send_24bit_data(pattern);
// 更新图案
if(direction == 0) {
// 向右移动
pattern <<= 1;
if(pattern == 0x00800000) { // 移动到第3片595的最高位
direction = 1;
}
} else {
// 向左移动
pattern >>= 1;
if(pattern == 0x00000001) { // 移动到第1片595的最低位
direction = 0;
}
}
delay_ms(100); // 控制流水速度
}
}
// 呼吸灯效果(使用OE引脚PWM控制)
void breathing_led(void) {
unsigned char brightness;
unsigned char direction = 0; // 0:变亮,1:变暗
// 设置所有LED亮
send_24bit_data(0xFFFFFF);
brightness = 0;
while(1) {
// PWM控制亮度
for(unsigned int i = 0; i < 255; i++) {
if(i < brightness) {
OE = 0; // 输出使能
} else {
OE = 1; // 输出关闭
}
delay_us(20); // PWM周期约5ms
}
// 更新亮度值
if(direction == 0) {
brightness++;
if(brightness == 255) {
direction = 1;
}
} else {
brightness--;
if(brightness == 0) {
direction = 0;
}
}
}
}
void main(void) {
// 初始化引脚
DS = 0;
SCK = 0;
RCK = 0;
OE = 0; // 输出使能
// 运行效果
// led_flow_effect();
breathing_led();
}
4.5 级联应用:8位数码管显示
c
// 8位数码管显示控制(使用3片595:2片用于段选,1片用于位选)
#include <reg51.h>
// 引脚定义
sbit SER = P1^0; // 串行数据
sbit RCLK = P1^1; // 存储时钟
sbit SRCLK = P1^2; // 移位时钟
// 数码管段码表(共阴极)
unsigned char code segment_code[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F, // 9
0x77, // A
0x7C, // B
0x39, // C
0x5E, // D
0x79, // E
0x71, // F
0x40, // -(负号)
0x00 // 空白
};
// 位选码表(选择哪一位数码管亮)
unsigned char code digit_code[] = {
0xFE, // 第1位
0xFD, // 第2位
0xFB, // 第3位
0xF7, // 第4位
0xEF, // 第5位
0xDF, // 第6位
0xBF, // 第7位
0x7F // 第8位
};
// 向595发送一个字节
void send_byte(unsigned char dat) {
unsigned char i;
for(i = 0; i < 8; i++) {
SER = dat & 0x80; // 取最高位
dat <<= 1; // 左移一位
// 产生移位时钟上升沿
SRCLK = 0;
SRCLK = 1;
}
}
// 显示8位数
void display_numbers(unsigned char *numbers) {
unsigned char i;
for(i = 0; i < 8; i++) {
// 发送位选数据(选择当前数码管)
send_byte(digit_code[i]);
// 发送段选数据(显示的数字)
send_byte(segment_code[numbers[i]]);
// 锁存数据到输出
RCLK = 0;
RCLK = 1;
// 短暂延时,实现动态扫描
delay_ms(2);
// 消隐(防止重影)
send_byte(0xFF); // 关闭所有位选
send_byte(0x00); // 关闭所有段选
RCLK = 0;
RCLK = 1;
}
}
// 显示一个8位整数
void display_integer(unsigned long number) {
unsigned char digits[8];
unsigned char i;
// 分离各位数字
for(i = 0; i < 8; i++) {
digits[7 - i] = number % 10;
number /= 10;
}
// 显示
display_numbers(digits);
}
void main(void) {
unsigned long counter = 0;
while(1) {
// 显示计数器值
display_integer(counter);
// 计数器递增
counter++;
if(counter > 99999999) {
counter = 0;
}
}
}
五、74HC595与51单片机的连接与编程
5.1 硬件连接电路
5.1.1 单芯片连接
51单片机 74HC595
P3.4 (任意IO) ───────→ DS (14)
P3.6 (任意IO) ───────→ SCK (11)
P3.5 (任意IO) ───────→ RCK (12)
P3.7 (任意IO) ───────→ OE (13) [可选]
MR (10) ───→ VCC
GND (8) ───→ GND
VCC (16) ───→ 5V
Q0-Q7 ────→ LED1-LED8(通过限流电阻)
5.1.2 限流电阻计算
对于LED应用,需要计算合适的限流电阻:
电阻值 R = (VCC - Vf_LED) / I_LED
假设:
VCC = 5V
LED正向电压 Vf = 2.0V
期望电流 I = 10mA
则:
R = (5 - 2) / 0.01 = 300Ω
可选择330Ω的标准电阻。
5.2 基础驱动程序
c
// 74HC595基础驱动库
#include <reg51.h>
#include <intrins.h>
// 引脚定义(根据实际连接修改)
sbit HC595_DS = P3^4; // 串行数据
sbit HC595_SCK = P3^6; // 移位时钟
sbit HC595_RCK = P3^5; // 存储时钟
sbit HC595_OE = P3^7; // 输出使能(可选)
// 初始化函数
void HC595_Init(void) {
HC595_DS = 0;
HC595_SCK = 0;
HC595_RCK = 0;
HC595_OE = 0; // 默认输出使能
}
// 向595发送一个字节
void HC595_WriteByte(unsigned char dat) {
unsigned char i;
for(i = 0; i < 8; i++) {
// 设置数据位(从最高位开始)
HC595_DS = (dat & 0x80) ? 1 : 0;
dat <<= 1;
// 产生移位时钟上升沿
HC595_SCK = 0;
_nop_(); _nop_(); // 短暂延时
HC595_SCK = 1;
_nop_(); _nop_();
HC595_SCK = 0;
}
// 产生存储时钟上升沿,锁存数据
HC595_RCK = 0;
_nop_(); _nop_();
HC595_RCK = 1;
_nop_(); _nop_();
HC595_RCK = 0;
}
// 向级联的595发送多个字节
void HC595_WriteBytes(unsigned char *dat, unsigned char len) {
unsigned char i, j;
// 发送所有字节
for(i = 0; i < len; i++) {
unsigned char byte_data = dat[len - 1 - i]; // 最后一片先发送
for(j = 0; j < 8; j++) {
HC595_DS = (byte_data & 0x80) ? 1 : 0;
byte_data <<= 1;
HC595_SCK = 0;
_nop_();
HC595_SCK = 1;
_nop_();
HC595_SCK = 0;
}
}
// 所有数据发送完毕后锁存
HC595_RCK = 0;
_nop_();
HC595_RCK = 1;
_nop_();
HC595_RCK = 0;
}
// 控制输出使能(用于PWM调光)
void HC595_SetOutputEnable(unsigned char enable) {
HC595_OE = enable ? 0 : 1; // 0=使能,1=关闭
}
// 测试函数:LED流水灯
void Test_LED_Flow(void) {
unsigned char patterns[] = {
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
0x40, 0x20, 0x10, 0x08, 0x04, 0x02
};
unsigned char i;
while(1) {
for(i = 0; i < sizeof(patterns); i++) {
HC595_WriteByte(patterns[i]);
delay_ms(100); // 控制流水速度
}
}
}
// 测试函数:二进制计数器
void Test_Binary_Counter(void) {
unsigned char counter = 0;
while(1) {
HC595_WriteByte(counter);
counter++;
delay_ms(200); // 计数速度
}
}
// 延时函数
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 114; j++);
}
// 主函数
void main(void) {
HC595_Init(); // 初始化595
// 选择测试函数
// Test_LED_Flow();
Test_Binary_Counter();
}
5.3 高级应用:8x8 LED点阵控制
c
// 8x8 LED点阵控制(使用2片595:1片控制行,1片控制列)
#include <reg51.h>
// 引脚定义
sbit SER = P1^0;
sbit RCLK = P1^1;
sbit SRCLK = P1^2;
// 点阵显示缓冲区
unsigned char display_buffer[8] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 向2片595发送16位数据
void send_16bit(unsigned int data) {
unsigned char i;
for(i = 0; i < 16; i++) {
SER = (data & 0x8000) ? 1 : 0;
data <<= 1;
SRCLK = 0;
SRCLK = 1;
}
RCLK = 0;
RCLK = 1;
}
// 显示一帧
void display_frame(void) {
unsigned char row;
unsigned int row_data, col_data;
for(row = 0; row < 8; row++) {
// 行数据:只有当前行对应的位为0(共阴极点阵)或1(共阳极点阵)
row_data = ~(1 << row); // 假设共阳极,低电平有效
// 列数据:从显示缓冲区获取
col_data = display_buffer[row];
// 组合行和列数据
// 注意:根据具体硬件连接可能需要调整顺序
unsigned int combined_data = (col_data << 8) | row_data;
// 发送到595
send_16bit(combined_data);
// 短暂延时,维持显示
delay_us(200);
// 消隐
send_16bit(0xFFFF);
}
}
// 在点阵上显示一个字符
void display_char(unsigned char ascii_code) {
// 字符点阵数据(8x8,只定义了0-9)
unsigned char code font_8x8[10][8] = {
// 0
{0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C},
// 1
{0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x08, 0x3E},
// 2
{0x3C, 0x42, 0x02, 0x04, 0x08, 0x10, 0x20, 0x7E},
// 3
{0x3C, 0x42, 0x02, 0x1C, 0x02, 0x02, 0x42, 0x3C},
// 4
{0x04, 0x0C, 0x14, 0x24, 0x44, 0x7E, 0x04, 0x04},
// 5
{0x7E, 0x40, 0x40, 0x7C, 0x02, 0x02, 0x42, 0x3C},
// 6
{0x3C, 0x42, 0x40, 0x7C, 0x42, 0x42, 0x42, 0x3C},
// 7
{0x7E, 0x02, 0x04, 0x08, 0x10, 0x10, 0x10, 0x10},
// 8
{0x3C, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x42, 0x3C},
// 9
{0x3C, 0x42, 0x42, 0x42, 0x3E, 0x02, 0x42, 0x3C}
};
if(ascii_code >= '0' && ascii_code <= '9') {
unsigned char index = ascii_code - '0';
unsigned char i;
// 复制字符数据到显示缓冲区
for(i = 0; i < 8; i++) {
display_buffer[i] = font_8x8[index][i];
}
}
}
// 滚动显示文本
void scroll_text(char *text, unsigned char length) {
unsigned char i, j, k;
// 为每个字符创建扩展缓冲区(字符之间用空白列分隔)
unsigned char extended_buffer[8][100]; // 假设最多100列
// 构建扩展显示缓冲区
unsigned char col_index = 0;
for(i = 0; i < length; i++) {
// 获取当前字符的点阵数据
unsigned char char_data[8];
if(text[i] >= '0' && text[i] <= '9') {
unsigned char idx = text[i] - '0';
for(j = 0; j < 8; j++) {
char_data[j] = font_8x8[idx][j];
}
} else {
// 空格字符
for(j = 0; j < 8; j++) {
char_data[j] = 0x00;
}
}
// 将字符数据添加到扩展缓冲区(每列8位)
for(j = 0; j < 8; j++) {
extended_buffer[0][col_index + j] = (char_data[0] >> (7 - j)) & 0x01;
extended_buffer[1][col_index + j] = (char_data[1] >> (7 - j)) & 0x01;
extended_buffer[2][col_index + j] = (char_data[2] >> (7 - j)) & 0x01;
extended_buffer[3][col_index + j] = (char_data[3] >> (7 - j)) & 0x01;
extended_buffer[4][col_index + j] = (char_data[4] >> (7 - j)) & 0x01;
extended_buffer[5][col_index + j] = (char_data[5] >> (7 - j)) & 0x01;
extended_buffer[6][col_index + j] = (char_data[6] >> (7 - j)) & 0x01;
extended_buffer[7][col_index + j] = (char_data[7] >> (7 - j)) & 0x01;
}
col_index += 8;
// 字符之间加一列空白
if(i < length - 1) {
for(j = 0; j < 8; j++) {
extended_buffer[j][col_index] = 0;
}
col_index++;
}
}
// 滚动显示
unsigned char total_cols = col_index;
for(i = 0; i < total_cols - 8; i++) { // 8列为一屏
// 更新显示缓冲区
for(j = 0; j < 8; j++) {
unsigned char col_data = 0;
for(k = 0; k < 8; k++) {
if(extended_buffer[j][i + k]) {
col_data |= (1 << (7 - k));
}
}
display_buffer[j] = col_data;
}
// 显示当前帧
for(k = 0; k < 50; k++) { // 每帧显示50次
display_frame();
}
}
}
void main(void) {
unsigned char counter = 0;
while(1) {
// 显示数字0-9
for(counter = 0; counter < 10; counter++) {
display_char('0' + counter);
// 显示一段时间
unsigned int t;
for(t = 0; t < 1000; t++) {
display_frame();
}
}
// 滚动显示文本
char text[] = "0123456789";
scroll_text(text, 10);
}
}
六、74HC595在Arduino平台上的使用
6.1 Arduino与74HC595的连接
cpp
// Arduino连接74HC595
const int latchPin = 8; // RCK (存储时钟)
const int clockPin = 12; // SCK (移位时钟)
const int dataPin = 11; // DS (串行数据)
const int oePin = 9; // OE (输出使能,可选)
void setup() {
// 设置引脚模式
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(oePin, OUTPUT);
// 默认输出使能
digitalWrite(oePin, LOW);
Serial.begin(9600);
}
// 向单个595发送数据
void shiftOut595(uint8_t data) {
// 拉低latchPin,准备发送数据
digitalWrite(latchPin, LOW);
// 使用Arduino内置函数发送数据
shiftOut(dataPin, clockPin, MSBFIRST, data);
// 拉高latchPin,锁存数据到输出
digitalWrite(latchPin, HIGH);
}
// 向多个级联的595发送数据
void shiftOutMultiple595(uint8_t *data, int numChips) {
digitalWrite(latchPin, LOW);
// 从最后一个芯片开始发送
for(int i = numChips - 1; i >= 0; i--) {
shiftOut(dataPin, clockPin, MSBFIRST, data[i]);
}
digitalWrite(latchPin, HIGH);
}
// PWM调光控制
void pwmControl(int brightness) {
// 设置所有LED亮
shiftOut595(0xFF);
// 使用OE引脚进行PWM控制
for(int i = 0; i < 255; i++) {
if(i < brightness) {
digitalWrite(oePin, LOW);
} else {
digitalWrite(oePin, HIGH);
}
delayMicroseconds(20);
}
}
// 呼吸灯效果
void breathingEffect() {
int brightness = 0;
int fadeAmount = 1;
while(true) {
// 调用PWM控制
for(int i = 0; i < 100; i++) { // 100个PWM周期
pwmControl(brightness);
}
// 改变亮度
brightness += fadeAmount;
// 反转渐变方向
if(brightness <= 0 || brightness >= 255) {
fadeAmount = -fadeAmount;
}
}
}
// 二进制计数器
void binaryCounter() {
static uint8_t counter = 0;
shiftOut595(counter);
counter++;
delay(200); // 计数间隔
}
// 串口控制595输出
void serialControl() {
if(Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
if(input.startsWith("SET ")) {
// 格式: SET <二进制值>
String binaryStr = input.substring(4);
uint8_t value = strtol(binaryStr.c_str(), NULL, 2);
shiftOut595(value);
Serial.print("Set to: ");
Serial.println(binaryStr);
}
else if(input == "CLEAR") {
shiftOut595(0x00);
Serial.println("Cleared");
}
else if(input == "ALL") {
shiftOut595(0xFF);
Serial.println("All ON");
}
}
}
void loop() {
// 运行不同的效果
static int mode = 0;
switch(mode) {
case 0:
binaryCounter();
break;
case 1:
breathingEffect();
break;
case 2:
serialControl();
break;
}
// 每10秒切换模式
static unsigned long lastModeChange = 0;
if(millis() - lastModeChange > 10000) {
mode = (mode + 1) % 3;
lastModeChange = millis();
Serial.print("Mode changed to: ");
Serial.println(mode);
}
}
6.2 Arduino库封装
cpp
// HC595.h - 74HC595 Arduino库头文件
#ifndef HC595_H
#define HC595_H
#include <Arduino.h>
class HC595 {
private:
uint8_t latchPin;
uint8_t clockPin;
uint8_t dataPin;
uint8_t oePin;
uint8_t numChips;
uint8_t *buffer;
public:
// 构造函数
HC595(uint8_t latch, uint8_t clock, uint8_t data, uint8_t oe = 255, uint8_t chips = 1);
// 析构函数
~HC595();
// 初始化
void begin();
// 向指定芯片写入数据
void write(uint8_t chip, uint8_t data);
// 写入所有芯片
void writeAll(uint8_t *data);
// 设置单个引脚
void setPin(uint8_t chip, uint8_t pin, bool state);
// 清除所有输出
void clear();
// 设置所有输出
void setAll();
// 获取当前数据
uint8_t read(uint8_t chip);
// 设置输出使能
void setOutputEnable(bool enable);
// 移位操作
void shiftLeft(uint8_t chip);
void shiftRight(uint8_t chip);
// 更新输出(将缓冲区数据发送到芯片)
void update();
};
#endif
cpp
// HC595.cpp - 74HC595 Arduino库实现
#include "HC595.h"
HC595::HC595(uint8_t latch, uint8_t clock, uint8_t data, uint8_t oe, uint8_t chips) {
latchPin = latch;
clockPin = clock;
dataPin = data;
oePin = oe;
numChips = chips;
// 分配缓冲区
buffer = new uint8_t[numChips];
memset(buffer, 0, numChips);
}
HC595::~HC595() {
delete[] buffer;
}
void HC595::begin() {
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
if(oePin != 255) {
pinMode(oePin, OUTPUT);
digitalWrite(oePin, LOW); // 默认使能输出
}
clear();
update();
}
void HC595::write(uint8_t chip, uint8_t data) {
if(chip < numChips) {
buffer[chip] = data;
}
}
void HC595::writeAll(uint8_t *data) {
for(uint8_t i = 0; i < numChips; i++) {
buffer[i] = data[i];
}
}
void HC595::setPin(uint8_t chip, uint8_t pin, bool state) {
if(chip < numChips && pin < 8) {
if(state) {
buffer[chip] |= (1 << pin);
} else {
buffer[chip] &= ~(1 << pin);
}
}
}
void HC595::clear() {
memset(buffer, 0, numChips);
}
void HC595::setAll() {
memset(buffer, 0xFF, numChips);
}
uint8_t HC595::read(uint8_t chip) {
if(chip < numChips) {
return buffer[chip];
}
return 0;
}
void HC595::setOutputEnable(bool enable) {
if(oePin != 255) {
digitalWrite(oePin, enable ? LOW : HIGH);
}
}
void HC595::shiftLeft(uint8_t chip) {
if(chip < numChips) {
buffer[chip] = (buffer[chip] << 1) | (buffer[chip] >> 7);
}
}
void HC595::shiftRight(uint8_t chip) {
if(chip < numChips) {
buffer[chip] = (buffer[chip] >> 1) | (buffer[chip] << 7);
}
}
void HC595::update() {
digitalWrite(latchPin, LOW);
// 从最后一个芯片开始发送
for(int i = numChips - 1; i >= 0; i--) {
shiftOut(dataPin, clockPin, MSBFIRST, buffer[i]);
}
digitalWrite(latchPin, HIGH);
}
// 使用示例
#include "HC595.h"
// 创建对象:2个级联的595
HC595 shiftReg(8, 12, 11, 9, 2);
void setup() {
shiftReg.begin();
// 测试:设置不同的模式
Serial.begin(9600);
}
void loop() {
static uint8_t pattern = 0x01;
// 写入第一个595
shiftReg.write(0, pattern);
// 写入第二个595
shiftReg.write(1, ~pattern);
// 更新输出
shiftReg.update();
// 左移模式
pattern = (pattern << 1) | (pattern >> 7);
delay(200);
// 通过串口控制
if(Serial.available()) {
char cmd = Serial.read();
switch(cmd) {
case '0':
shiftReg.clear();
shiftReg.update();
break;
case '1':
shiftReg.setAll();
shiftReg.update();
break;
case '2':
// 切换输出使能
static bool oeState = true;
shiftReg.setOutputEnable(oeState);
oeState = !oeState;
break;
}
}
}
七、74HC595在实际项目中的应用案例
7.1 案例一:智能家居控制面板
c
// 智能家居控制面板 - 控制8个继电器
#include <reg51.h>
// 引脚定义
sbit DS = P1^0;
sbit SCK = P1^1;
sbit RCK = P1^2;
sbit OE = P1^3;
// 按键输入
sbit KEY1 = P2^0;
sbit KEY2 = P2^1;
sbit KEY3 = P2^2;
sbit KEY4 = P2^3;
// 继电器状态
unsigned char relay_state = 0x00;
// 设备名称
char* device_names[] = {
"客厅灯", "卧室灯", "厨房灯", "卫生间灯",
"空调", "电视", "窗帘", "音响"
};
// 发送数据到595
void send_to_595(unsigned char data) {
unsigned char i;
for(i = 0; i < 8; i++) {
DS = (data & 0x80) ? 1 : 0;
data <<= 1;
SCK = 0;
SCK = 1;
}
RCK = 0;
RCK = 1;
}
// 初始化
void init_system(void) {
// 初始化595
DS = 0;
SCK = 0;
RCK = 0;
OE = 0; // 输出使能
// 初始化按键(上拉输入)
KEY1 = 1;
KEY2 = 1;
KEY3 = 1;
KEY4 = 1;
// 初始关闭所有继电器
relay_state = 0x00;
send_to_595(relay_state);
}
// 按键扫描
unsigned char scan_keys(void) {
unsigned char key_val = 0;
// 简单按键扫描(实际应用中应加去抖)
if(!KEY1) key_val |= 0x01;
if(!KEY2) key_val |= 0x02;
if(!KEY3) key_val |= 0x04;
if(!KEY4) key_val |= 0x08;
return key_val;
}
// 控制指定继电器
void control_relay(unsigned char relay_num, unsigned char state) {
if(relay_num < 8) {
if(state) {
relay_state |= (1 << relay_num);
} else {
relay_state &= ~(1 << relay_num);
}
send_to_595(relay_state);
}
}
// 切换继电器状态
void toggle_relay(unsigned char relay_num) {
if(relay_num < 8) {
relay_state ^= (1 << relay_num);
send_to_595(relay_state);
}
}
// 场景模式
void set_scene(unsigned char scene) {
switch(scene) {
case 0: // 回家模式
relay_state = 0x0F; // 打开前4个灯
break;
case 1: // 离家模式
relay_state = 0x00; // 关闭所有
break;
case 2: // 观影模式
relay_state = 0x81; // 打开客厅灯和音响
break;
case 3: // 睡眠模式
relay_state = 0x02; // 只打开卧室灯
break;
}
send_to_595(relay_state);
}
// 串口通信(与上位机通信)
void uart_init(void) {
SCON = 0x50; // 模式1,允许接收
TMOD = 0x20; // 定时器1模式2
TH1 = 0xFD; // 波特率9600
TL1 = 0xFD;
TR1 = 1; // 启动定时器1
EA = 1; // 开总中断
ES = 1; // 开串口中断
}
// 串口中断服务函数
void uart_isr(void) interrupt 4 {
if(RI) {
unsigned char cmd = SBUF;
RI = 0;
// 处理上位机命令
switch(cmd) {
case 'A': // 获取状态
SBUF = relay_state;
break;
case 'B': // 打开所有
relay_state = 0xFF;
send_to_595(relay_state);
SBUF = 'O';
break;
case 'C': // 关闭所有
relay_state = 0x00;
send_to_595(relay_state);
SBUF = 'C';
break;
default:
if(cmd >= '0' && cmd <= '7') {
// 控制单个设备
unsigned char dev = cmd - '0';
toggle_relay(dev);
SBUF = 'T';
}
break;
}
}
}
// 主函数
void main(void) {
unsigned char last_keys = 0;
unsigned char scene_index = 0;
init_system();
uart_init();
while(1) {
// 扫描按键
unsigned char keys = scan_keys();
// 检测按键变化
if(keys != last_keys) {
// 按键1-4控制前4个设备
if((keys & 0x01) && !(last_keys & 0x01)) {
toggle_relay(0);
}
if((keys & 0x02) && !(last_keys & 0x02)) {
toggle_relay(1);
}
if((keys & 0x04) && !(last_keys & 0x04)) {
toggle_relay(2);
}
if((keys & 0x08) && !(last_keys & 0x08)) {
toggle_relay(3);
}
last_keys = keys;
}
// 定时切换场景(每30秒)
static unsigned long last_scene_change = 0;
if(get_tick_count() - last_scene_change > 30000) {
set_scene(scene_index);
scene_index = (scene_index + 1) % 4;
last_scene_change = get_tick_count();
}
}
}
7.2 案例二:工业控制信号灯系统
c
// 工业控制信号灯系统 - 控制16个信号灯
#include <reg51.h>
// 引脚定义
sbit DS = P3^0;
sbit SCK = P3^1;
sbit RCK = P3^2;
sbit OE = P3^3;
// 传感器输入
sbit SENSOR1 = P1^0;
sbit SENSOR2 = P1^1;
sbit SENSOR3 = P1^2;
sbit EMERGENCY_BTN = P1^3;
// 系统状态
typedef enum {
STATE_NORMAL,
STATE_WARNING,
STATE_ERROR,
STATE_EMERGENCY
} SystemState;
SystemState current_state = STATE_NORMAL;
// 向2个级联的595发送16位数据
void send_16bit(unsigned int data) {
unsigned char i;
for(i = 0; i < 16; i++) {
DS = (data & 0x8000) ? 1 : 0;
data <<= 1;
SCK = 0;
SCK = 1;
}
RCK = 0;
RCK = 1;
}
// 初始化
void init_system(void) {
DS = 0;
SCK = 0;
RCK = 0;
OE = 0;
// 初始化传感器输入
SENSOR1 = 1;
SENSOR2 = 1;
SENSOR3 = 1;
EMERGENCY_BTN = 1;
// 初始状态:所有灯灭
send_16bit(0x0000);
}
// 读取传感器状态
unsigned char read_sensors(void) {
unsigned char sensor_state = 0;
if(!SENSOR1) sensor_state |= 0x01;
if(!SENSOR2) sensor_state |= 0x02;
if(!SENSOR3) sensor_state |= 0x04;
if(!EMERGENCY_BTN) sensor_state |= 0x08;
return sensor_state;
}
// 正常状态显示
void normal_display(void) {
// 绿色灯常亮
static unsigned int pattern = 0x00FF; // 低8位为绿色灯
static unsigned char direction = 0;
// 流水灯效果
if(direction == 0) {
pattern = (pattern << 1) | (pattern >> 15);
if(pattern == 0x8000) direction = 1;
} else {
pattern = (pattern >> 1) | (pattern << 15);
if(pattern == 0x00FF) direction = 0;
}
send_16bit(pattern);
}
// 警告状态显示
void warning_display(void) {
// 黄色灯闪烁
static unsigned char blink = 0;
static unsigned int pattern = 0xFF00; // 高8位为黄色灯
if(blink) {
send_16bit(pattern);
} else {
send_16bit(0x0000);
}
blink = !blink;
}
// 错误状态显示
void error_display(void) {
// 红色灯快速闪烁
static unsigned char blink = 0;
static unsigned int pattern = 0xAAAA; // 红绿交替
if(blink) {
send_16bit(pattern);
} else {
send_16bit(0x5555); // 反相交替
}
blink = !blink;
}
// 紧急状态显示
void emergency_display(void) {
// 所有红色灯快速闪烁
static unsigned char blink = 0;
unsigned int pattern = 0xFFFF; // 所有灯亮
if(blink) {
send_16bit(pattern);
} else {
send_16bit(0x0000);
}
blink = !blink;
}
// 状态机处理
void state_machine(void) {
unsigned char sensors = read_sensors();
// 状态转移
switch(current_state) {
case STATE_NORMAL:
if(sensors & 0x08) { // 紧急按钮按下
current_state = STATE_EMERGENCY;
} else if(sensors & 0x07) { // 任一传感器触发
current_state = STATE_WARNING;
}
normal_display();
break;
case STATE_WARNING:
if(sensors & 0x08) {
current_state = STATE_EMERGENCY;
} else if(!(sensors & 0x07)) { // 所有传感器正常
current_state = STATE_NORMAL;
} else if((sensors & 0x07) == 0x07) { // 所有传感器触发
current_state = STATE_ERROR;
}
warning_display();
break;
case STATE_ERROR:
if(sensors & 0x08) {
current_state = STATE_EMERGENCY;
} else if(!(sensors & 0x07)) {
current_state = STATE_NORMAL;
}
error_display();
break;
case STATE_EMERGENCY:
if(!(sensors & 0x08)) { // 紧急按钮释放
current_state = STATE_NORMAL;
}
emergency_display();
break;
}
}
// 主函数
void main(void) {
init_system();
while(1) {
// 运行状态机
state_machine();
// 延时控制刷新率
switch(current_state) {
case STATE_NORMAL:
delay_ms(200); // 正常状态更新较慢
break;
case STATE_WARNING:
delay_ms(500); // 警告状态0.5秒闪烁
break;
case STATE_ERROR:
delay_ms(250); // 错误状态0.25秒闪烁
break;
case STATE_EMERGENCY:
delay_ms(100); // 紧急状态0.1秒闪烁
break;
}
}
}
八、74HC595常见问题与解答
8.1 常见问题及解决方法
问题1:595芯片发热严重
可能原因:
- 输出电流过大
- 多个输出同时为高或低
- 电源电压过高
解决方法:
c
// 1. 增加限流电阻
// LED串联电阻计算:R = (Vcc - Vf) / I
// 如5V电源,LED压降2V,电流10mA:R = (5-2)/0.01 = 300Ω
// 2. 分时复用输出,避免所有输出同时工作
void time_multiplexing(void) {
// 每次只使能部分输出
static unsigned char group = 0;
switch(group) {
case 0:
HC595_WriteByte(0x0F); // 低4位
break;
case 1:
HC595_WriteByte(0xF0); // 高4位
break;
}
group = (group + 1) % 2;
}
// 3. 使用外部驱动器
// 当需要更大电流时,使用ULN2803、达林顿管等驱动
问题2:输出有重影/干扰
可能原因:
- 时序问题
- 电源噪声
- 信号线干扰
解决方法:
c
// 1. 优化时序
void send_data_optimized(unsigned char data) {
// 先关闭输出
OE = 1;
// 发送数据
HC595_WriteByte(data);
// 短暂延时后使能输出
delay_us(10);
OE = 0;
}
// 2. 增加去耦电容
// 在VCC和GND之间加0.1μF陶瓷电容和10μF电解电容
// 3. 使用屏蔽线或双绞线
// 长距离传输时,使用屏蔽电缆
问题3:级联时数据传输错误
可能原因:
- 时序不匹配
- 级联顺序错误
- 电源问题
解决方法:
c
// 1. 确保时序正确
void send_to_cascade(unsigned char *data, unsigned char num) {
unsigned char i, j;
// 更严格的时序控制
for(i = 0; i < num; i++) {
unsigned char byte = data[num-1-i]; // 最后一片先发
for(j = 0; j < 8; j++) {
DS = (byte & 0x80) ? 1 : 0;
byte <<= 1;
// 确保时钟低电平时间足够
SCK = 0;
delay_us(2); // 增加延时
SCK = 1;
delay_us(2);
SCK = 0;
}
}
// 数据锁存前增加延时
delay_us(5);
RCK = 0;
delay_us(2);
RCK = 1;
delay_us(2);
RCK = 0;
}
// 2. 检查级联顺序
// 第一片的Q7'连接第二片的DS
// 所有片的SCK、RCK并联
// 3. 每片595单独供电去耦
8.2 调试技巧
技巧1:使用示波器调试
c
// 创建测试信号函数
void generate_test_pattern(void) {
// 发送特定的测试模式
unsigned char test_patterns[] = {
0xAA, // 10101010 - 交替模式
0x55, // 01010101 - 交替模式反相
0xF0, // 11110000 - 高4位/低4位
0x0F, // 00001111
0x81, // 10000001 - 两边
0x18, // 00011000 - 中间
0xFF, // 11111111 - 全亮
0x00 // 00000000 - 全灭
};
for(int i = 0; i < 8; i++) {
HC595_WriteByte(test_patterns[i]);
delay_ms(1000); // 每1秒切换,便于测量
}
}
技巧2:软件模拟SPI调试
c
// 软件模拟SPI,便于调试
void software_spi_debug(unsigned char data) {
unsigned char i;
printf("Sending byte: 0x%02X\n", data);
printf("Bit stream: ");
for(i = 0; i < 8; i++) {
unsigned char bit = (data >> (7-i)) & 0x01;
printf("%d", bit);
// 实际发送
DS = bit;
SCK = 0;
SCK = 1;
}
printf("\n");
// 锁存
RCK = 0;
RCK = 1;
printf("Data latched to output\n");
}
8.3 性能优化技巧
优化1:使用硬件SPI(如果单片机支持)
c
// 使用硬件SPI(如STM32、AVR等)
void hardware_spi_send(unsigned char data) {
// 等待SPI空闲
while(!SPI_Ready());
// 发送数据
SPI_SendData(data);
// 锁存到输出
RCK = 0;
RCK = 1;
}
// 批量发送优化
void bulk_send(unsigned char *data, unsigned int len) {
// 使用DMA或批量发送
RCK = 0;
for(unsigned int i = 0; i < len; i++) {
hardware_spi_send_no_latch(data[i]);
}
RCK = 1; // 一次锁存所有数据
}
优化2:使用中断和DMA
c
// 中断方式发送数据
volatile unsigned char tx_buffer[32];
volatile unsigned char tx_index = 0;
volatile unsigned char tx_len = 0;
void spi_interrupt_handler(void) {
if(tx_index < tx_len) {
SPI_SendData(tx_buffer[tx_index++]);
if(tx_index == tx_len) {
// 所有数据发送完毕,锁存
RCK = 0;
RCK = 1;
tx_index = 0;
tx_len = 0;
}
}
}
// 异步发送函数
void async_send(unsigned char *data, unsigned char len) {
// 复制数据到缓冲区
for(int i = 0; i < len; i++) {
tx_buffer[i] = data[i];
}
tx_len = len;
// 启动发送
if(tx_len > 0) {
SPI_SendData(tx_buffer[tx_index++]);
}
}
九、总结
9.1 74HC595核心要点回顾
- 串入并出:74HC595的核心功能是将串行数据转换为并行输出
- 三大部分:移位寄存器、存储寄存器、输出缓冲器
- 级联能力:通过Q7'引脚支持多芯片级联扩展
- 输出控制:OE引脚控制输出使能,可用于PWM调光
- 高驱动能力:每个输出引脚可提供最大35mA电流
9.2 选择74HC595的考量因素
- 应用需求:需要多少路输出?需要多大驱动电流?
- 系统资源:单片机有多少可用IO口?通信接口是否充足?
- 成本考虑:595成本低,但需要较多外围元件
- 扩展性:未来是否需要增加更多输出?
9.3 与其他方案的对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 74HC595 | 成本低、使用简单、驱动能力强 | 占用IO口、速度较慢 | LED控制、继电器控制、IO扩展 |
| I2C GPIO扩展 | 占用IO少、可寻址、有中断 | 驱动能力弱、成本较高 | 传感器扩展、按键扫描 |
| SPI GPIO扩展 | 速度快、可级联 | 协议复杂、占用IO | 高速IO扩展、显示器控制 |
| 专用驱动芯片 | 集成度高、功能丰富 | 成本高、灵活性差 | 专业显示驱动、电机控制 |
9.4 未来发展趋势
- 集成化:将595与LED驱动、保护电路集成
- 智能化:增加I2C/SPI接口,减少IO占用
- 高密度:更多输出通道的单芯片解决方案
- 低功耗:针对电池供电应用的优化版本
9.5 学习建议
- 从基础开始:先掌握单芯片使用,再学习级联
- 动手实践:通过实际项目加深理解
- 阅读数据手册:深入理解芯片特性
- 学习相关技术:掌握SPI、I2C等通信协议
- 关注新技术:了解新型IO扩展方案
附录:完整项目示例
A.1 基于74HC595的智能温湿度显示系统
c
// 完整项目:智能温湿度显示系统
#include <reg51.h>
#include <stdio.h>
// 引脚定义
sbit DS = P3^4;
sbit SCK = P3^5;
sbit RCK = P3^6;
sbit DHT11_DATA = P2^0; // 温湿度传感器
sbit BUZZER = P2^1; // 蜂鸣器
// 数码管段码(共阴极)
unsigned char code seg_table[] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F, // 9
0x77, // A
0x7C, // b
0x39, // C
0x5E, // d
0x79, // E
0x71, // F
0x40, // -
0x00 // 空白
};
// 温度单位符号
unsigned char code temp_unit[] = {
0x63, // °C (0x39|0x40)
0x39, // C
0x40 // -
};
// 显示缓冲区
unsigned char display_buffer[8] = {0};
// 向595发送数据
void hc595_send(unsigned char dat) {
unsigned char i;
for(i = 0; i < 8; i++) {
DS = (dat & 0x80) ? 1 : 0;
dat <<= 1;
SCK = 0;
SCK = 1;
}
RCK = 0;
RCK = 1;
}
// 数码管显示函数
void display_digit(unsigned char pos, unsigned char num, unsigned char dp) {
unsigned char seg_data = seg_table[num];
if(dp) {
seg_data |= 0x80; // 点亮小数点
}
// 先发送段选数据
hc595_send(seg_data);
// 再发送位选数据(只有当前位为0)
unsigned char bit_data = ~(1 << pos);
hc595_send(bit_data);
// 消隐(防止重影)
delay_ms(1);
hc595_send(0x00);
hc595_send(0xFF);
}
// DHT11读取函数
bit read_dht11(unsigned char *temp, unsigned char *humi) {
unsigned char i, j;
unsigned char data[5] = {0};
// 主机发出开始信号
DHT11_DATA = 0;
delay_ms(18); // 至少18ms
DHT11_DATA = 1;
delay_us(30); // 等待20-40us
// DHT11响应
if(!DHT11_DATA) {
delay_us(80);
if(DHT11_DATA) {
delay_us(80);
// 读取40位数据
for(i = 0; i < 5; i++) {
for(j = 0; j < 8; j++) {
while(!DHT11_DATA); // 等待50us低电平结束
delay_us(30); // 判断高电平持续时间
data[i] <<= 1;
if(DHT11_DATA) {
data[i] |= 1;
while(DHT11_DATA); // 等待高电平结束
}
}
}
// 校验数据
if(data[4] == (data[0] + data[1] + data[2] + data[3])) {
*humi = data[0];
*temp = data[2];
return 1;
}
}
}
return 0;
}
// 更新显示缓冲区
void update_display_buffer(unsigned char temp, unsigned char humi) {
// 显示格式: T 25.5 H 60.5
// 位置: 0 1 2 3 4 5 6 7
// T
display_buffer[0] = 20; // 自定义字符'T'
// 温度十位
display_buffer[1] = temp / 10;
// 温度个位(带小数点)
display_buffer[2] = temp % 10;
// 小数点位置(在个位后)
// 在display_digit函数中控制
// 温度小数位
display_buffer[3] = 5; // 示例数据
// H
display_buffer[4] = 21; // 自定义字符'H'
// 湿度十位
display_buffer[5] = humi / 10;
// 湿度个位(带小数点)
display_buffer[6] = humi % 10;
// 湿度小数位
display_buffer[7] = 5; // 示例数据
}
// 蜂鸣器报警
void buzzer_alarm(unsigned char temp) {
if(temp > 30) {
// 温度过高报警
BUZZER = 0;
delay_ms(100);
BUZZER = 1;
delay_ms(100);
} else if(temp < 10) {
// 温度过低报警
BUZZER = 0;
delay_ms(500);
BUZZER = 1;
delay_ms(500);
}
}
// 主函数
void main(void) {
unsigned char temperature = 25;
unsigned char humidity = 60;
unsigned char read_success;
// 初始化
DS = 0;
SCK = 0;
RCK = 0;
BUZZER = 1; // 蜂鸣器关闭
// 初始显示
update_display_buffer(temperature, humidity);
while(1) {
// 读取温湿度
read_success = read_dht11(&temperature, &humidity);
if(read_success) {
// 更新显示
update_display_buffer(temperature, humidity);
// 检查是否需要报警
buzzer_alarm(temperature);
}
// 动态扫描显示
for(unsigned char i = 0; i < 8; i++) {
unsigned char dp = 0;
// 在第2位和第6位显示小数点
if(i == 2 || i == 6) {
dp = 1;
}
display_digit(i, display_buffer[i], dp);
}
// 延时(控制刷新率)
delay_ms(100);
}
}
// 延时函数
void delay_us(unsigned int us) {
while(us--) {
_nop_(); _nop_(); _nop_(); _nop_();
}
}
void delay_ms(unsigned int ms) {
unsigned int i, j;
for(i = 0; i < ms; i++) {
for(j = 0; j < 114; j++);
}
}
A.2 项目总结
本项目展示了74HC595在实际应用中的完整使用:
- 硬件设计:合理连接595、传感器、执行器
- 软件架构:模块化设计,易于维护和扩展
- 功能实现:数据采集、处理、显示、控制一体化
- 用户体验:直观的显示界面和及时的报警提示
通过这个项目,你可以学习到:
- 74HC595与数码管的配合使用
- 传感器数据的采集和处理
- 多任务系统的简单实现
- 完整嵌入式项目的开发流程
结束语:74HC595虽然是一个简单的芯片,但其应用非常广泛。掌握好这个芯片的使用,不仅能解决实际项目中的IO扩展问题,还能为学习更复杂的数字电路和嵌入式系统打下坚实基础。希望本文能对你有所帮助,祝你在嵌入式开发的道路上越走越远!