BLOG8
跳转至

数字电路基础:逻辑门与时序电路

学习目标

完成本文学习后,你将能够:

  • 理解数字电路的基本概念和二进制逻辑
  • 掌握基本逻辑门(AND、OR、NOT、XOR等)的功能和真值表
  • 理解组合逻辑电路的设计方法
  • 掌握触发器的工作原理和类型
  • 理解时序逻辑电路的基本概念
  • 能够进行简单的时序分析

前置要求

在开始本文学习之前,你需要:

知识要求: - 了解基本的电学概念(电压、电流) - 理解二进制数制(0和1) - 了解基本的布尔代数概念 - 熟悉基本的逻辑运算

技能要求: - 能够阅读简单的电路图 - 了解基本的电路符号 - 理解真值表的概念

推荐但非必需: - 有一定的编程基础(理解逻辑运算) - 了解基本的电子元件 - 熟悉示波器的使用

概述

数字电路是现代电子系统的基础,从简单的逻辑门到复杂的微处理器,都是由数字电路构成的。与模拟电路处理连续信号不同,数字电路只处理离散的二进制信号(0和1),这使得数字电路具有抗干扰能力强、易于设计和集成的优点。

为什么要学习数字电路

  1. 嵌入式系统基础:微控制器内部都是数字电路
  2. 接口设计:GPIO、总线等都基于数字逻辑
  3. 时序理解:理解时钟、建立时间、保持时间等概念
  4. FPGA/CPLD开发:数字电路是硬件描述语言的基础
  5. 故障排查:理解数字信号的特性有助于调试

数字电路的基本特点

特性 模拟电路 数字电路
信号类型 连续信号 离散信号(0/1)
抗干扰性 较弱 较强
精度 受元件影响大 由位数决定
设计复杂度 较高 相对简单
集成度 较低 很高
功耗 相对较低 动态功耗较高

第一部分:数字逻辑基础

二进制与逻辑电平

数字电路使用二进制表示信息,只有两个状态:

逻辑电平定义:

逻辑1(高电平):
- TTL:2.0V - 5.0V
- CMOS 3.3V:2.0V - 3.6V
- CMOS 5V:3.5V - 5.5V

逻辑0(低电平):
- TTL:0V - 0.8V
- CMOS 3.3V:0V - 0.8V
- CMOS 5V:0V - 1.5V

不确定区域(禁区):
- 介于逻辑0和逻辑1之间的电压
- 应避免信号停留在此区域

正逻辑与负逻辑:

正逻辑:
- 高电平 = 逻辑1
- 低电平 = 逻辑0
- 最常用的约定

负逻辑:
- 高电平 = 逻辑0
- 低电平 = 逻辑1
- 某些特殊应用使用

布尔代数基础

布尔代数是数字电路的数学基础,只有三种基本运算:

1. 与运算(AND):

符号:A · B 或 A ∧ B 或 AB
C语言:A && B 或 A & B
规则:只有所有输入都为1时,输出才为1

2. 或运算(OR):

符号:A + B 或 A ∨ B
C语言:A || B 或 A | B
规则:只要有一个输入为1,输出就为1

3. 非运算(NOT):

符号:Ā 或 A' 或 ¬A
C语言:!A 或 ~A
规则:输入为1时输出为0,输入为0时输出为1

基本定律:

交换律:
A + B = B + A
A · B = B · A

结合律:
(A + B) + C = A + (B + C)
(A · B) · C = A · (B · C)

分配律:
A · (B + C) = A · B + A · C
A + (B · C) = (A + B) · (A + C)

德摩根定律:
(A + B)' = A' · B'
(A · B)' = A' + B'

恒等律:
A + 0 = A
A · 1 = A
A + 1 = 1
A · 0 = 0

互补律:
A + A' = 1
A · A' = 0

幂等律:
A + A = A
A · A = A

第二部分:基本逻辑门

1. 与门(AND Gate)

与门实现逻辑与运算,只有所有输入都为1时,输出才为1。

电路符号:

    A ──┐
        ├──D──── Y = A · B
    B ──┘

真值表:

A B Y = A · B
0 0 0
0 1 0
1 0 0
1 1 1

C语言实现:

/**
 * @brief  与门逻辑
 * @param  a: 输入A(0或1)
 * @param  b: 输入B(0或1)
 * @retval 输出Y
 */
uint8_t and_gate(uint8_t a, uint8_t b) {
    return a & b;  // 位与运算
}

// 使用示例
void and_gate_example(void) {
    printf("AND Gate Truth Table:\n");
    printf("A | B | Y\n");
    printf("--|---|--\n");
    for (int a = 0; a <= 1; a++) {
        for (int b = 0; b <= 1; b++) {
            printf("%d | %d | %d\n", a, b, and_gate(a, b));
        }
    }
}

应用场景: - 使能控制:只有使能信号有效时,数据才能通过 - 条件判断:多个条件同时满足时执行操作 - 位掩码:提取特定位的信息

2. 或门(OR Gate)

或门实现逻辑或运算,只要有一个输入为1,输出就为1。

电路符号:

    A ──┐
        ├≥1─── Y = A + B
    B ──┘

真值表:

A B Y = A + B
0 0 0
0 1 1
1 0 1
1 1 1

C语言实现:

/**
 * @brief  或门逻辑
 * @param  a: 输入A(0或1)
 * @param  b: 输入B(0或1)
 * @retval 输出Y
 */
uint8_t or_gate(uint8_t a, uint8_t b) {
    return a | b;  // 位或运算
}

应用场景: - 中断源合并:多个中断源触发同一个中断 - 错误标志:任何一个错误发生都置位错误标志 - 唤醒源:多个唤醒源中任一个有效都唤醒系统

3. 非门(NOT Gate / Inverter)

非门实现逻辑非运算,输出是输入的反相。

电路符号:

    A ──▷○─── Y = A'

真值表:

A Y = A'
0 1
1 0

C语言实现:

/**
 * @brief  非门逻辑
 * @param  a: 输入A(0或1)
 * @retval 输出Y
 */
uint8_t not_gate(uint8_t a) {
    return !a;  // 逻辑非运算
    // 或者:return ~a & 0x01;  // 位非运算(需要屏蔽高位)
}

应用场景: - 信号反相:将高电平有效信号转换为低电平有效 - 逻辑取反:实现相反的逻辑功能 - 时钟反相:产生反相时钟信号

4. 与非门(NAND Gate)

与非门是与门后接非门,是最基本的逻辑门之一(可以用它构造所有其他逻辑门)。

电路符号:

    A ──┐
        ├──D○─── Y = (A · B)'
    B ──┘

真值表:

A B Y = (A · B)'
0 0 1
0 1 1
1 0 1
1 1 0

C语言实现:

/**
 * @brief  与非门逻辑
 * @param  a: 输入A(0或1)
 * @param  b: 输入B(0或1)
 * @retval 输出Y
 */
uint8_t nand_gate(uint8_t a, uint8_t b) {
    return !(a & b);  // 与非运算
}

重要性: - NAND门是"功能完备"的,可以用它实现任何逻辑函数 - 在CMOS工艺中,NAND门比NOR门更容易实现 - 许多数字IC内部大量使用NAND门

5. 或非门(NOR Gate)

或非门是或门后接非门,也是功能完备的逻辑门。

电路符号:

    A ──┐
        ├≥1○─── Y = (A + B)'
    B ──┘

真值表:

A B Y = (A + B)'
0 0 1
0 1 0
1 0 0
1 1 0

C语言实现:

/**
 * @brief  或非门逻辑
 * @param  a: 输入A(0或1)
 * @param  b: 输入B(0或1)
 * @retval 输出Y
 */
uint8_t nor_gate(uint8_t a, uint8_t b) {
    return !(a | b);  // 或非运算
}

6. 异或门(XOR Gate)

异或门输出两个输入不同时为1,相同时为0。

电路符号:

    A ──┐
        ├=1─── Y = A ⊕ B
    B ──┘

真值表:

A B Y = A ⊕ B
0 0 0
0 1 1
1 0 1
1 1 0

C语言实现:

/**
 * @brief  异或门逻辑
 * @param  a: 输入A(0或1)
 * @param  b: 输入B(0或1)
 * @retval 输出Y
 */
uint8_t xor_gate(uint8_t a, uint8_t b) {
    return a ^ b;  // 异或运算
}

应用场景: - 奇偶校验:检测数据传输错误 - 比较器:判断两个信号是否相同 - 加法器:实现二进制加法的基础 - 数据加密:简单的加密算法

7. 同或门(XNOR Gate)

同或门是异或门的反相,两个输入相同时输出为1。

电路符号:

    A ──┐
        ├=1○─── Y = (A ⊕ B)'
    B ──┘

真值表:

A B Y = (A ⊕ B)'
0 0 1
0 1 0
1 0 0
1 1 1

C语言实现:

/**
 * @brief  同或门逻辑
 * @param  a: 输入A(0或1)
 * @param  b: 输入B(0或1)
 * @retval 输出Y
 */
uint8_t xnor_gate(uint8_t a, uint8_t b) {
    return !(a ^ b);  // 同或运算
}

应用场景: - 相等比较:判断两个信号是否相等 - 数据校验:验证数据一致性

逻辑门总结

逻辑门对比表:

门类型 符号 逻辑表达式 特点 常用场景
AND D Y = A · B 全1出1 使能控制
OR ≥1 Y = A + B 有1出1 信号合并
NOT ▷○ Y = A' 反相 信号取反
NAND D○ Y = (A · B)' 功能完备 通用逻辑
NOR ≥1○ Y = (A + B)' 功能完备 通用逻辑
XOR =1 Y = A ⊕ B 不同出1 校验/比较
XNOR =1○ Y = (A ⊕ B)' 相同出1 相等判断

逻辑门实现示例:

/**
 * @brief  完整的逻辑门库
 */
typedef struct {
    uint8_t (*and_gate)(uint8_t a, uint8_t b);
    uint8_t (*or_gate)(uint8_t a, uint8_t b);
    uint8_t (*not_gate)(uint8_t a);
    uint8_t (*nand_gate)(uint8_t a, uint8_t b);
    uint8_t (*nor_gate)(uint8_t a, uint8_t b);
    uint8_t (*xor_gate)(uint8_t a, uint8_t b);
    uint8_t (*xnor_gate)(uint8_t a, uint8_t b);
} logic_gates_t;

// 逻辑门测试函数
void test_all_logic_gates(void) {
    printf("Complete Logic Gates Truth Tables:\n\n");

    printf("AND | OR  | NOT | NAND| NOR | XOR | XNOR\n");
    printf("A B Y | A B Y | A Y | A B Y | A B Y | A B Y | A B Y\n");
    printf("------|-------|-----|-------|-------|-------|------\n");

    for (int a = 0; a <= 1; a++) {
        for (int b = 0; b <= 1; b++) {
            printf("%d %d %d | ", a, b, and_gate(a, b));
            printf("%d %d %d | ", a, b, or_gate(a, b));
            if (b == 0) printf("%d %d | ", a, not_gate(a));
            else printf("    | ");
            printf("%d %d %d | ", a, b, nand_gate(a, b));
            printf("%d %d %d | ", a, b, nor_gate(a, b));
            printf("%d %d %d | ", a, b, xor_gate(a, b));
            printf("%d %d %d\n", a, b, xnor_gate(a, b));
        }
    }
}

第三部分:组合逻辑电路

组合逻辑电路的输出只取决于当前输入,不依赖于历史状态。

1. 半加器(Half Adder)

半加器实现两个一位二进制数的加法,产生和(Sum)与进位(Carry)。

逻辑表达式:

Sum = A ⊕ B
Carry = A · B

真值表:

A B Sum Carry
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1

C语言实现:

/**
 * @brief  半加器
 * @param  a: 输入A
 * @param  b: 输入B
 * @param  sum: 输出和
 * @param  carry: 输出进位
 */
void half_adder(uint8_t a, uint8_t b, uint8_t *sum, uint8_t *carry) {
    *sum = a ^ b;      // 异或得到和
    *carry = a & b;    // 与得到进位
}

// 使用示例
void half_adder_example(void) {
    uint8_t sum, carry;

    printf("Half Adder Truth Table:\n");
    printf("A | B | Sum | Carry\n");
    printf("--|---|-----|------\n");

    for (int a = 0; a <= 1; a++) {
        for (int b = 0; b <= 1; b++) {
            half_adder(a, b, &sum, &carry);
            printf("%d | %d |  %d  |   %d\n", a, b, sum, carry);
        }
    }
}

2. 全加器(Full Adder)

全加器在半加器基础上增加了进位输入,可以实现多位加法。

逻辑表达式:

Sum = A ⊕ B ⊕ Cin
Cout = (A · B) + (Cin · (A ⊕ B))

真值表:

A B Cin Sum Cout
0 0 0 0 0
0 0 1 1 0
0 1 0 1 0
0 1 1 0 1
1 0 0 1 0
1 0 1 0 1
1 1 0 0 1
1 1 1 1 1

C语言实现:

/**
 * @brief  全加器
 * @param  a: 输入A
 * @param  b: 输入B
 * @param  cin: 进位输入
 * @param  sum: 输出和
 * @param  cout: 输出进位
 */
void full_adder(uint8_t a, uint8_t b, uint8_t cin, 
                uint8_t *sum, uint8_t *cout) {
    *sum = a ^ b ^ cin;
    *cout = (a & b) | (cin & (a ^ b));
}

/**
 * @brief  4位二进制加法器
 * @param  a: 4位输入A
 * @param  b: 4位输入B
 * @retval 5位结果(包含进位)
 */
uint8_t adder_4bit(uint8_t a, uint8_t b) {
    uint8_t sum = 0;
    uint8_t carry = 0;
    uint8_t s, c;

    for (int i = 0; i < 4; i++) {
        uint8_t bit_a = (a >> i) & 0x01;
        uint8_t bit_b = (b >> i) & 0x01;

        full_adder(bit_a, bit_b, carry, &s, &c);

        sum |= (s << i);
        carry = c;
    }

    // 最高位是进位
    sum |= (carry << 4);

    return sum;
}

// 使用示例
void adder_example(void) {
    uint8_t a = 0b1010;  // 10
    uint8_t b = 0b0111;  // 7
    uint8_t result = adder_4bit(a, b);

    printf("4-bit Adder: %d + %d = %d\n", a, b, result);
    // 输出:4-bit Adder: 10 + 7 = 17
}

3. 多路选择器(Multiplexer, MUX)

多路选择器根据选择信号从多个输入中选择一个输出。

2选1多路选择器:

逻辑表达式:
Y = (S' · A) + (S · B)

当S=0时,Y=A
当S=1时,Y=B

真值表:

S A B Y
0 0 X 0
0 1 X 1
1 X 0 0
1 X 1 1

C语言实现:

/**
 * @brief  2选1多路选择器
 * @param  a: 输入A
 * @param  b: 输入B
 * @param  sel: 选择信号(0选A,1选B)
 * @retval 输出Y
 */
uint8_t mux_2to1(uint8_t a, uint8_t b, uint8_t sel) {
    return sel ? b : a;
    // 或者:return (!sel & a) | (sel & b);
}

/**
 * @brief  4选1多路选择器
 * @param  inputs: 4个输入的数组
 * @param  sel: 2位选择信号(0-3)
 * @retval 输出Y
 */
uint8_t mux_4to1(uint8_t inputs[4], uint8_t sel) {
    return inputs[sel & 0x03];
}

// 使用示例
void mux_example(void) {
    uint8_t inputs[4] = {0xAA, 0xBB, 0xCC, 0xDD};

    printf("4-to-1 Multiplexer:\n");
    for (int sel = 0; sel < 4; sel++) {
        printf("SEL=%d: Output=0x%02X\n", sel, mux_4to1(inputs, sel));
    }
}

应用场景: - 数据选择:从多个数据源中选择一个 - 总线切换:在多个设备间切换总线访问 - 功能选择:根据配置选择不同的功能模块

4. 译码器(Decoder)

译码器将二进制编码转换为独热码(one-hot)输出。

2-4译码器:

输入:2位二进制(A1 A0)
输出:4个独热码输出(Y3 Y2 Y1 Y0)

真值表:

A1 A0 Y3 Y2 Y1 Y0
0 0 0 0 0 1
0 1 0 0 1 0
1 0 0 1 0 0
1 1 1 0 0 0

C语言实现:

/**
 * @brief  2-4译码器
 * @param  input: 2位输入(0-3)
 * @retval 4位独热码输出
 */
uint8_t decoder_2to4(uint8_t input) {
    return 1 << (input & 0x03);
}

/**
 * @brief  3-8译码器
 * @param  input: 3位输入(0-7)
 * @retval 8位独热码输出
 */
uint8_t decoder_3to8(uint8_t input) {
    return 1 << (input & 0x07);
}

// 使用示例
void decoder_example(void) {
    printf("2-to-4 Decoder:\n");
    printf("Input | Output (binary)\n");
    printf("------|----------------\n");

    for (int i = 0; i < 4; i++) {
        uint8_t output = decoder_2to4(i);
        printf("  %d   | ", i);
        for (int j = 3; j >= 0; j--) {
            printf("%d", (output >> j) & 0x01);
        }
        printf("\n");
    }
}

应用场景: - 地址译码:将地址信号转换为片选信号 - 指令译码:CPU中将指令码转换为控制信号 - 显示驱动:驱动7段数码管等显示器件

第四部分:时序逻辑电路

时序逻辑电路的输出不仅取决于当前输入,还取决于电路的历史状态。时序电路的核心是存储元件——触发器。

时序电路的基本概念

时钟信号(Clock):

时钟是时序电路的心跳,控制电路的状态变化时刻

     ┌───┐   ┌───┐   ┌───┐
CLK  │   │   │   │   │   │
   ──┘   └───┘   └───┘   └───

关键参数:
- 频率(Frequency):每秒钟的周期数
- 周期(Period):一个完整周期的时间
- 占空比(Duty Cycle):高电平时间占周期的比例
- 上升沿(Rising Edge):从低到高的跳变
- 下降沿(Falling Edge):从高到低的跳变

同步与异步:

同步电路:
- 所有触发器共用同一个时钟
- 状态变化发生在时钟沿
- 易于设计和分析
- 现代数字系统的主流

异步电路:
- 没有统一的时钟
- 状态变化由输入信号直接触发
- 设计复杂,但速度可能更快
- 用于特殊场合

触发器类型

1. SR锁存器(SR Latch)

SR锁存器是最基本的存储单元,有置位(Set)和复位(Reset)两个输入。

逻辑表达式:

Q = S + R' · Q
Q' = R + S' · Q'

真值表:

S R Q(next) 说明
0 0 Q(保持) 保持当前状态
0 1 0 复位
1 0 1 置位
1 1 X 禁止状态

C语言模拟:

/**
 * @brief  SR锁存器状态
 */
typedef struct {
    uint8_t Q;      // 输出Q
    uint8_t Q_bar;  // 输出Q'
} sr_latch_t;

/**
 * @brief  SR锁存器更新
 * @param  latch: 锁存器状态
 * @param  S: 置位输入
 * @param  R: 复位输入
 */
void sr_latch_update(sr_latch_t *latch, uint8_t S, uint8_t R) {
    if (S && R) {
        // 禁止状态,不更新
        printf("Warning: S=1, R=1 is forbidden!\n");
        return;
    }

    if (S) {
        latch->Q = 1;
        latch->Q_bar = 0;
    } else if (R) {
        latch->Q = 0;
        latch->Q_bar = 1;
    }
    // S=0, R=0时保持不变
}

2. D触发器(D Flip-Flop)

D触发器是最常用的触发器,在时钟沿到来时将D输入的值存储到Q输出。

特性表:

CLK D Q(next)
↑ 0 0
↑ 1 1
0/1 X Q(保持)

C语言模拟:

/**
 * @brief  D触发器状态
 */
typedef struct {
    uint8_t Q;          // 输出Q
    uint8_t clk_prev;   // 上一次时钟状态
} d_flipflop_t;

/**
 * @brief  D触发器初始化
 */
void d_flipflop_init(d_flipflop_t *ff) {
    ff->Q = 0;
    ff->clk_prev = 0;
}

/**
 * @brief  D触发器更新(上升沿触发)
 * @param  ff: 触发器状态
 * @param  D: 数据输入
 * @param  clk: 时钟输入
 */
void d_flipflop_update(d_flipflop_t *ff, uint8_t D, uint8_t clk) {
    // 检测上升沿
    if (clk && !ff->clk_prev) {
        ff->Q = D;  // 在上升沿锁存D的值
    }
    ff->clk_prev = clk;
}

// 使用示例
void d_flipflop_example(void) {
    d_flipflop_t ff;
    d_flipflop_init(&ff);

    printf("D Flip-Flop Simulation:\n");
    printf("CLK | D | Q\n");
    printf("----+---+---\n");

    // 模拟时钟序列
    uint8_t clk_seq[] = {0, 1, 0, 1, 0, 1, 0, 1};
    uint8_t d_seq[]   = {1, 1, 0, 0, 1, 1, 0, 0};

    for (int i = 0; i < 8; i++) {
        d_flipflop_update(&ff, d_seq[i], clk_seq[i]);
        printf(" %d  | %d | %d\n", clk_seq[i], d_seq[i], ff.Q);
    }
}

应用场景: - 数据寄存:暂存数据 - 移位寄存器:串行数据传输 - 计数器:实现各种计数功能 - 状态机:存储状态信息

3. JK触发器(JK Flip-Flop)

JK触发器是SR触发器的改进版本,消除了禁止状态。

特性表:

CLK J K Q(next) 说明
↑ 0 0 Q 保持
↑ 0 1 0 复位
↑ 1 0 1 置位
↑ 1 1 Q' 翻转

C语言模拟:

/**
 * @brief  JK触发器状态
 */
typedef struct {
    uint8_t Q;
    uint8_t clk_prev;
} jk_flipflop_t;

/**
 * @brief  JK触发器更新
 */
void jk_flipflop_update(jk_flipflop_t *ff, uint8_t J, uint8_t K, uint8_t clk) {
    if (clk && !ff->clk_prev) {  // 上升沿
        if (J && K) {
            ff->Q = !ff->Q;  // 翻转
        } else if (J) {
            ff->Q = 1;       // 置位
        } else if (K) {
            ff->Q = 0;       // 复位
        }
        // J=0, K=0时保持
    }
    ff->clk_prev = clk;
}

4. T触发器(T Flip-Flop)

T触发器在T=1时翻转状态,T=0时保持状态。

特性表:

CLK T Q(next)
↑ 0 Q
↑ 1 Q'

C语言模拟:

/**
 * @brief  T触发器更新
 */
void t_flipflop_update(d_flipflop_t *ff, uint8_t T, uint8_t clk) {
    if (clk && !ff->clk_prev) {  // 上升沿
        if (T) {
            ff->Q = !ff->Q;  // 翻转
        }
    }
    ff->clk_prev = clk;
}

应用场景: - 分频器:将时钟频率除以2 - 计数器:实现二进制计数

时序电路应用

1. 寄存器(Register)

寄存器是由多个D触发器组成的,用于存储多位数据。

8位寄存器实现:

/**
 * @brief  8位寄存器
 */
typedef struct {
    uint8_t data;       // 存储的数据
    uint8_t clk_prev;   // 上一次时钟状态
} register_8bit_t;

/**
 * @brief  寄存器初始化
 */
void register_init(register_8bit_t *reg) {
    reg->data = 0;
    reg->clk_prev = 0;
}

/**
 * @brief  寄存器更新
 * @param  reg: 寄存器
 * @param  input: 输入数据
 * @param  clk: 时钟信号
 * @param  enable: 使能信号(1=允许写入,0=保持)
 */
void register_update(register_8bit_t *reg, uint8_t input, 
                     uint8_t clk, uint8_t enable) {
    if (clk && !reg->clk_prev && enable) {
        reg->data = input;
    }
    reg->clk_prev = clk;
}

// 使用示例
void register_example(void) {
    register_8bit_t reg;
    register_init(&reg);

    printf("8-bit Register Simulation:\n");
    printf("CLK | EN | Input | Output\n");
    printf("----+----+-------+-------\n");

    // 模拟写入序列
    uint8_t clk_seq[] = {0, 1, 0, 1, 0, 1, 0, 1};
    uint8_t en_seq[]  = {1, 1, 1, 1, 0, 0, 1, 1};
    uint8_t in_seq[]  = {0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC, 0xDD, 0xDD};

    for (int i = 0; i < 8; i++) {
        register_update(&reg, in_seq[i], clk_seq[i], en_seq[i]);
        printf(" %d  | %d  | 0x%02X  | 0x%02X\n", 
               clk_seq[i], en_seq[i], in_seq[i], reg.data);
    }
}

2. 移位寄存器(Shift Register)

移位寄存器可以将数据左移或右移,用于串并转换。

4位移位寄存器:

/**
 * @brief  4位移位寄存器
 */
typedef struct {
    uint8_t data;       // 4位数据
    uint8_t clk_prev;
} shift_register_4bit_t;

/**
 * @brief  移位寄存器初始化
 */
void shift_register_init(shift_register_4bit_t *sr) {
    sr->data = 0;
    sr->clk_prev = 0;
}

/**
 * @brief  右移操作
 * @param  sr: 移位寄存器
 * @param  serial_in: 串行输入(从左边移入)
 * @param  clk: 时钟信号
 */
void shift_register_right(shift_register_4bit_t *sr, 
                          uint8_t serial_in, uint8_t clk) {
    if (clk && !sr->clk_prev) {
        sr->data = (sr->data >> 1) | ((serial_in & 0x01) << 3);
        sr->data &= 0x0F;  // 保持4位
    }
    sr->clk_prev = clk;
}

/**
 * @brief  左移操作
 */
void shift_register_left(shift_register_4bit_t *sr, 
                         uint8_t serial_in, uint8_t clk) {
    if (clk && !sr->clk_prev) {
        sr->data = ((sr->data << 1) | (serial_in & 0x01)) & 0x0F;
    }
    sr->clk_prev = clk;
}

// 使用示例
void shift_register_example(void) {
    shift_register_4bit_t sr;
    shift_register_init(&sr);

    printf("4-bit Shift Register (Right Shift):\n");
    printf("CLK | Serial_In | Data (binary)\n");
    printf("----+-----------+--------------\n");

    uint8_t clk_seq[] = {0, 1, 0, 1, 0, 1, 0, 1};
    uint8_t in_seq[]  = {1, 1, 0, 0, 1, 1, 0, 0};

    for (int i = 0; i < 8; i++) {
        shift_register_right(&sr, in_seq[i], clk_seq[i]);
        printf(" %d  |     %d     | ", clk_seq[i], in_seq[i]);
        for (int j = 3; j >= 0; j--) {
            printf("%d", (sr.data >> j) & 0x01);
        }
        printf("\n");
    }
}

应用场景: - 串并转换:将串行数据转换为并行数据 - 并串转换:将并行数据转换为串行数据 - 延迟线:延迟数据若干个时钟周期 - LED驱动:级联驱动多个LED

3. 计数器(Counter)

计数器是时序电路的重要应用,可以实现各种计数功能。

4位二进制计数器:

/**
 * @brief  4位二进制计数器
 */
typedef struct {
    uint8_t count;      // 计数值(0-15)
    uint8_t clk_prev;
} counter_4bit_t;

/**
 * @brief  计数器初始化
 */
void counter_init(counter_4bit_t *cnt) {
    cnt->count = 0;
    cnt->clk_prev = 0;
}

/**
 * @brief  计数器更新(上升沿加1)
 * @param  cnt: 计数器
 * @param  clk: 时钟信号
 * @param  reset: 复位信号(1=复位)
 * @param  enable: 使能信号(1=计数,0=保持)
 */
void counter_update(counter_4bit_t *cnt, uint8_t clk, 
                    uint8_t reset, uint8_t enable) {
    if (reset) {
        cnt->count = 0;
    } else if (clk && !cnt->clk_prev && enable) {
        cnt->count = (cnt->count + 1) & 0x0F;  // 模16计数
    }
    cnt->clk_prev = clk;
}

/**
 * @brief  模N计数器
 */
void counter_mod_n(counter_4bit_t *cnt, uint8_t clk, 
                   uint8_t reset, uint8_t enable, uint8_t modulo) {
    if (reset) {
        cnt->count = 0;
    } else if (clk && !cnt->clk_prev && enable) {
        cnt->count++;
        if (cnt->count >= modulo) {
            cnt->count = 0;
        }
    }
    cnt->clk_prev = clk;
}

// 使用示例
void counter_example(void) {
    counter_4bit_t cnt;
    counter_init(&cnt);

    printf("4-bit Binary Counter:\n");
    printf("CLK | Count (dec) | Count (bin)\n");
    printf("----+-------------+------------\n");

    for (int i = 0; i < 20; i++) {
        uint8_t clk = i % 2;  // 生成时钟
        counter_update(&cnt, clk, 0, 1);

        if (clk) {  // 只在上升沿后打印
            printf(" %d  |     %2d      | ", clk, cnt.count);
            for (int j = 3; j >= 0; j--) {
                printf("%d", (cnt.count >> j) & 0x01);
            }
            printf("\n");
        }
    }
}

应用场景: - 分频器:产生低频时钟 - 定时器:实现定时功能 - 地址生成:产生顺序地址 - 状态机:实现状态计数

第五部分:时序分析基础

时序参数

在设计时序电路时,必须考虑以下关键时序参数:

1. 建立时间(Setup Time, tsu):

数据在时钟沿到来之前必须保持稳定的最小时间

     数据 ────┐         ┌────
              │  稳定   │
              └─────────┘
                 ↑
                tsu
                 ↑
     时钟 ───────┘ └─────────

2. 保持时间(Hold Time, th):

数据在时钟沿到来之后必须保持稳定的最小时间

     数据 ────────┐         ┌
                  │  稳定   │
                  └─────────┘
                  ↑
                  th
                  ↑
     时钟 ────────┘ └────────

3. 传播延迟(Propagation Delay, tpd):

从时钟沿到输出稳定的时间

     时钟 ────┐   ┌────
              │   │
              └───┘
              ↑
              │←─ tpd ─→│
              │         ↑
     输出 ─────X─────────┐
                         │
                         └────

4. 时钟周期约束:

时钟周期必须满足:
Tclk ≥ tpd(FF1) + tlogic + tsu(FF2)

其中:
- tpd(FF1):第一个触发器的传播延迟
- tlogic:组合逻辑延迟
- tsu(FF2):第二个触发器的建立时间

时序分析示例:

/**
 * @brief  时序参数定义
 */
typedef struct {
    float t_setup;      // 建立时间(ns)
    float t_hold;       // 保持时间(ns)
    float t_prop;       // 传播延迟(ns)
    float t_clk_to_q;   // 时钟到输出延迟(ns)
} timing_params_t;

/**
 * @brief  计算最大时钟频率
 * @param  params: 时序参数
 * @param  t_logic: 组合逻辑延迟(ns)
 * @retval 最大时钟频率(MHz)
 */
float calculate_max_frequency(timing_params_t *params, float t_logic) {
    // 最小时钟周期 = tclk_to_q + tlogic + tsetup
    float t_min = params->t_clk_to_q + t_logic + params->t_setup;

    // 最大频率 = 1 / 最小周期
    float f_max = 1000.0 / t_min;  // MHz

    printf("时序分析:\n");
    printf("  时钟到输出延迟: %.2f ns\n", params->t_clk_to_q);
    printf("  组合逻辑延迟: %.2f ns\n", t_logic);
    printf("  建立时间: %.2f ns\n", params->t_setup);
    printf("  最小时钟周期: %.2f ns\n", t_min);
    printf("  最大时钟频率: %.2f MHz\n", f_max);

    return f_max;
}

// 使用示例
void timing_analysis_example(void) {
    timing_params_t params = {
        .t_setup = 0.5,      // 0.5ns
        .t_hold = 0.3,       // 0.3ns
        .t_prop = 1.0,       // 1.0ns
        .t_clk_to_q = 0.8    // 0.8ns
    };

    float t_logic = 2.0;  // 组合逻辑延迟2ns

    float f_max = calculate_max_frequency(&params, t_logic);
    // 输出:最大时钟频率约303 MHz
}

亚稳态(Metastability)

当输入信号在建立时间或保持时间窗口内变化时,触发器可能进入亚稳态。

亚稳态特征:

- 输出电压处于不确定状态(既不是0也不是1)
- 输出可能振荡
- 最终会稳定到0或1,但时间不确定
- 可能导致系统错误

避免亚稳态的方法:

/**
 * @brief  双触发器同步器(防止亚稳态)
 * 
 * 异步信号 ──→ [FF1] ──→ [FF2] ──→ 同步信号
 *               ↑         ↑
 *              CLK       CLK
 * 
 * 原理:
 * - FF1可能进入亚稳态
 * - FF2有一个完整时钟周期来稳定
 * - 大大降低亚稳态传播的概率
 */
typedef struct {
    uint8_t ff1_q;
    uint8_t ff2_q;
    uint8_t clk_prev;
} synchronizer_t;

void synchronizer_init(synchronizer_t *sync) {
    sync->ff1_q = 0;
    sync->ff2_q = 0;
    sync->clk_prev = 0;
}

void synchronizer_update(synchronizer_t *sync, 
                         uint8_t async_input, uint8_t clk) {
    if (clk && !sync->clk_prev) {
        sync->ff2_q = sync->ff1_q;  // FF2锁存FF1的输出
        sync->ff1_q = async_input;   // FF1锁存异步输入
    }
    sync->clk_prev = clk;
}

uint8_t synchronizer_output(synchronizer_t *sync) {
    return sync->ff2_q;  // 输出经过两级同步的信号
}

最佳实践: - 异步信号必须经过同步器 - 使用两级或三级触发器同步 - 关键信号可以使用更多级 - 注意同步器引入的延迟

第六部分:实际应用与设计技巧

1. 按键消抖电路

机械按键在按下和释放时会产生抖动,需要消抖处理。

硬件消抖(SR锁存器):

按键按下时,通过SR锁存器锁定状态,避免抖动影响

     VCC
      │
      ├─── 按键 ──┬── R ──┐
      │           │       │
     10kΩ        10kΩ    SR锁存器
      │           │       │
     GND         GND     输出

软件消抖:

/**
 * @brief  按键消抖状态机
 */
typedef enum {
    KEY_IDLE,           // 空闲状态
    KEY_PRESS_DEBOUNCE, // 按下消抖
    KEY_PRESSED,        // 已按下
    KEY_RELEASE_DEBOUNCE // 释放消抖
} key_state_t;

typedef struct {
    key_state_t state;
    uint16_t counter;
    uint8_t key_value;
} key_debounce_t;

#define DEBOUNCE_TIME_MS  20  // 消抖时间20ms

/**
 * @brief  按键消抖处理(每1ms调用一次)
 * @param  key: 按键状态
 * @param  key_input: 当前按键输入(0=按下,1=释放)
 * @retval 稳定的按键值
 */
uint8_t key_debounce_process(key_debounce_t *key, uint8_t key_input) {
    switch (key->state) {
        case KEY_IDLE:
            if (key_input == 0) {  // 检测到按下
                key->state = KEY_PRESS_DEBOUNCE;
                key->counter = 0;
            }
            break;

        case KEY_PRESS_DEBOUNCE:
            if (key_input == 0) {
                key->counter++;
                if (key->counter >= DEBOUNCE_TIME_MS) {
                    key->state = KEY_PRESSED;
                    key->key_value = 1;  // 确认按下
                }
            } else {
                key->state = KEY_IDLE;  // 误触发
            }
            break;

        case KEY_PRESSED:
            if (key_input == 1) {  // 检测到释放
                key->state = KEY_RELEASE_DEBOUNCE;
                key->counter = 0;
            }
            break;

        case KEY_RELEASE_DEBOUNCE:
            if (key_input == 1) {
                key->counter++;
                if (key->counter >= DEBOUNCE_TIME_MS) {
                    key->state = KEY_IDLE;
                    key->key_value = 0;  // 确认释放
                }
            } else {
                key->state = KEY_PRESSED;  // 误触发
            }
            break;
    }

    return key->key_value;
}

2. 分频器设计

分频器用于产生低频时钟信号。

2分频器(T触发器):

/**
 * @brief  2分频器
 * 使用T触发器实现,每个时钟沿翻转一次
 */
typedef struct {
    uint8_t output;
    uint8_t clk_prev;
} divider_by_2_t;

void divider_by_2_update(divider_by_2_t *div, uint8_t clk) {
    if (clk && !div->clk_prev) {
        div->output = !div->output;  // 翻转
    }
    div->clk_prev = clk;
}

任意分频器:

/**
 * @brief  N分频器
 * @param  N: 分频系数
 */
typedef struct {
    uint16_t count;
    uint16_t modulo;
    uint8_t output;
    uint8_t clk_prev;
} divider_by_n_t;

void divider_by_n_init(divider_by_n_t *div, uint16_t N) {
    div->count = 0;
    div->modulo = N;
    div->output = 0;
    div->clk_prev = 0;
}

void divider_by_n_update(divider_by_n_t *div, uint8_t clk) {
    if (clk && !div->clk_prev) {
        div->count++;
        if (div->count >= div->modulo) {
            div->count = 0;
            div->output = !div->output;  // 翻转输出
        }
    }
    div->clk_prev = clk;
}

// 使用示例:10分频器
void divider_example(void) {
    divider_by_n_t div;
    divider_by_n_init(&div, 10);

    printf("10分频器输出:\n");
    for (int i = 0; i < 40; i++) {
        uint8_t clk = i % 2;
        divider_by_n_update(&div, clk);
        if (clk) {
            printf("输入时钟%2d, 输出=%d\n", i/2, div.output);
        }
    }
}

3. 脉冲展宽电路

将短脉冲展宽为较长的脉冲。

/**
 * @brief  脉冲展宽器
 * @param  width: 展宽时间(时钟周期数)
 */
typedef struct {
    uint16_t counter;
    uint16_t width;
    uint8_t output;
    uint8_t clk_prev;
} pulse_extender_t;

void pulse_extender_init(pulse_extender_t *ext, uint16_t width) {
    ext->counter = 0;
    ext->width = width;
    ext->output = 0;
    ext->clk_prev = 0;
}

void pulse_extender_update(pulse_extender_t *ext, 
                           uint8_t pulse_in, uint8_t clk) {
    if (clk && !ext->clk_prev) {
        if (pulse_in) {
            ext->counter = ext->width;  // 检测到脉冲,重新计数
            ext->output = 1;
        } else if (ext->counter > 0) {
            ext->counter--;
            if (ext->counter == 0) {
                ext->output = 0;  // 计数结束,输出归零
            }
        }
    }
    ext->clk_prev = clk;
}

4. 边沿检测电路

检测信号的上升沿或下降沿。

/**
 * @brief  边沿检测器
 */
typedef struct {
    uint8_t prev_value;
    uint8_t clk_prev;
} edge_detector_t;

void edge_detector_init(edge_detector_t *det) {
    det->prev_value = 0;
    det->clk_prev = 0;
}

/**
 * @brief  上升沿检测
 * @retval 1=检测到上升沿,0=无上升沿
 */
uint8_t detect_rising_edge(edge_detector_t *det, 
                           uint8_t signal, uint8_t clk) {
    uint8_t rising_edge = 0;

    if (clk && !det->clk_prev) {
        if (signal && !det->prev_value) {
            rising_edge = 1;  // 检测到上升沿
        }
        det->prev_value = signal;
    }
    det->clk_prev = clk;

    return rising_edge;
}

/**
 * @brief  下降沿检测
 */
uint8_t detect_falling_edge(edge_detector_t *det, 
                            uint8_t signal, uint8_t clk) {
    uint8_t falling_edge = 0;

    if (clk && !det->clk_prev) {
        if (!signal && det->prev_value) {
            falling_edge = 1;  // 检测到下降沿
        }
        det->prev_value = signal;
    }
    det->clk_prev = clk;

    return falling_edge;
}

总结

本文系统介绍了数字电路的基础知识,包括逻辑门和时序电路。

核心要点

  1. 基本逻辑门:
  2. AND、OR、NOT:基本运算
  3. NAND、NOR:功能完备
  4. XOR、XNOR:比较和校验

  5. 组合逻辑电路:

  6. 输出只取决于当前输入
  7. 加法器、多路选择器、译码器
  8. 无记忆功能

  9. 时序逻辑电路:

  10. 输出取决于当前输入和历史状态
  11. 触发器是基本存储单元
  12. 寄存器、计数器、移位寄存器

  13. 时序分析:

  14. 建立时间和保持时间
  15. 传播延迟
  16. 亚稳态问题

设计原则

  1. 组合逻辑设计:
  2. 化简逻辑表达式
  3. 减少门延迟
  4. 避免竞争冒险

  5. 时序逻辑设计:

  6. 同步设计优先
  7. 注意时序约束
  8. 异步信号必须同步

  9. 实际应用:

  10. 按键消抖
  11. 时钟分频
  12. 边沿检测

最佳实践

  1. 逻辑门使用:
  2. 优先使用NAND/NOR(工艺优势)
  3. 注意扇出限制
  4. 考虑传播延迟

  5. 触发器使用:

  6. D触发器最常用
  7. 统一使用上升沿或下降沿
  8. 添加复位功能

  9. 时序设计:

  10. 满足时序约束
  11. 使用同步器处理异步信号
  12. 避免组合逻辑环路

延伸阅读

推荐进一步学习的内容:

  1. 进阶主题:
  2. FPGA开发入门
  3. Verilog硬件描述语言
  4. 状态机设计

  5. 相关知识:

  6. 嵌入式电路基础:电阻、电容、电感
  7. PCB设计入门:从原理图到PCB
  8. 信号完整性分析基础

  9. 实践项目:

  10. 数字时钟设计
  11. 交通灯控制器

参考资料

  1. 《数字电子技术基础》- 阎石
  2. 《数字设计原理与实践》- John F. Wakerly
  3. 《Digital Design》- M. Morris Mano
  4. 《FPGA原理与应用》- 王金明

练习题:

  1. 使用基本逻辑门实现以下功能:
  2. 三输入多数表决器(输出为多数输入的值)
  3. 4位奇偶校验生成器
  4. 2-4译码器

  5. 设计一个模10计数器:

  6. 使用D触发器实现
  7. 添加异步复位功能
  8. 添加使能控制

  9. 分析以下电路的时序:

  10. 时钟频率:100MHz
  11. 触发器tclk_to_q:0.5ns
  12. 组合逻辑延迟:3ns
  13. 建立时间:0.3ns
  14. 计算最大工作频率

  15. 设计一个按键检测电路:

  16. 检测按键按下事件
  17. 实现消抖功能
  18. 输出单脉冲信号

实践任务:

  1. 使用C语言模拟一个4位全加器
  2. 实现一个8位移位寄存器
  3. 设计一个60进制计数器(秒计数)
  4. 实现一个简单的状态机(红绿灯控制)

下一步:建议学习 电源电路设计:LDO与DC-DC选择,了解如何为数字电路提供稳定的电源。


常见问题解答:

Q: 为什么NAND门和NOR门被称为"功能完备"?

A: 因为可以仅使用NAND门或NOR门实现所有其他逻辑门: - NOT: A NAND A = A' - AND: (A NAND B) NAND (A NAND B) = A · B - OR: (A NAND A) NAND (B NAND B) = A + B

这使得它们在IC设计中非常重要。

Q: 什么时候会发生亚稳态?如何避免?

A: 亚稳态发生在: - 输入信号在建立时间或保持时间窗口内变化 - 异步信号直接连接到触发器

避免方法: - 使用双触发器同步器 - 确保满足时序约束 - 关键信号使用三级同步

Q: 组合逻辑和时序逻辑有什么区别?

A: 主要区别: - 组合逻辑:输出只取决于当前输入,无记忆 - 时序逻辑:输出取决于当前输入和历史状态,有记忆

实际应用中,大多数电路都是两者的结合。

Q: 如何选择时钟频率?

A: 考虑因素: - 满足时序约束(建立时间、保持时间) - 功耗要求(频率越高功耗越大) - 系统性能需求 - 时钟分配网络的限制

通常留有20%-30%的时序余量。