学习目标
include “config.h”
define NIXIE_DI P44 // 数据输入
define NIXIE_SCK P42 // 移位寄存器
define NIXIE_RCK P43 // 锁存寄存器
define NIXIE_PIN_INIT() { P4M0 &= ~0x1c; P4M1 &= ~0x1c; }
void NIXIE_init();
// u8 a_dat = 0x12; // 0001 0010 字母位 // u8 b_idx = 0x1F; // 0001 1111 数字位 void NIXIE_show(u8 a_dat, u8 b_idx);
// num对应数字在数组里的位置(索引) // id 显示在指定位置(0 -> 7) void NIXIE_display(u8 num, u8 id);
endif
- 定义 `NIXIE_init()` 函数,负责对GPIO相关的初始化- 定义 `NIXIE_display()`函数负责显示```c#include "NIXIE.h"#include "Delay.h"#define GET_BIT_VAL(byte, pos) (byte & (1 << pos))//#define NOP_TIME() NOP40() // 用于看logic分析仪#define NOP_TIME() NOP2()// 锁存操作 - 多行宏定义#define RCK_ACTION() \NIXIE_RCK = 0; \NOP_TIME(); \NIXIE_RCK = 1; \NOP_TIME();void NIXIE_init(){NIXIE_PIN_INIT();}static void NIXIE_out(u8 dat){char i;// 8bit,先发出去的会作为高位for(i = 7; i >= 0; i--){NIXIE_DI = GET_BIT_VAL(dat, i);// 寄存器的移位操作NIXIE_SCK = 0;NOP_TIME(); // 休眠一会儿NIXIE_SCK = 1;NOP_TIME(); // 休眠一会儿}}// 每次可以显示多个,但是内容都是一样的a_dat// u8 a_dat = 0x12; // 0001 0010 字母位// u8 b_idx = 0x1F; // 0001 1111 数字位void NIXIE_show(u8 a_dat, u8 b_idx){// 显示 7.// 0111 1000// 先发字母位 (控制显示的内容) // 0点亮// 8bit,先发出去的会作为高位NIXIE_out(a_dat);// 0,1,2,3....7// 再发数字位 (控制显示哪几个) // 只要不是0,就是高电平// 1111 1011// 7.7.空7. 7.7.7.7. -------------------与二级制是反向// 8bit,先发出去的会作为高位NIXIE_out(b_idx);// 锁存操作RCK_ACTION();}
以上为Nixie.h的实现,也是对之前代码的封装处理。
#include "Config.h"#include "NIXIE.h"#include "Delay.h"int main() {u8 a_dat = 0xF8; // 0001 0010 字母位u8 b_idx = 0xFB; // 0001 1111 数字位NIXIE_init();// NIXIE_show写到循环外边即可NIXIE_show(a_dat, b_idx);while(1) {}}
以上为 main.c中使用我们封装的驱动。以上代码就会很简洁。
封装的一些疑问
封装的特点
封装是面向对象程序设计中的一个重要概念,它将数据和行为封装在一起,形成一个独立的单元。下面是封装的特点:
- 数据隐藏:封装可以隐藏数据,只对外界公开必要的接口,从而保证数据的安全性和可靠性。
- 接口统一:封装可以将数据和行为组织在一起,形成一个类或对象,通过统一的接口对外提供服务,便于使用和管理。
- 信息隐藏:封装可以隐藏实现细节,只对外界公开必要的信息,从而降低了程序的复杂度和耦合度,提高了程序的可维护性和可扩展性。
- 可重用性:封装可以将数据和行为封装成一个独立的单元,便于复用和重复利用,提高了程序的开发效率和代码的可重用性。
- 封装和继承相结合:封装和继承是面向对象程序设计中的两个重要概念,它们相互配合,可以构建出更加复杂、灵活和可扩展的程序。
总之,封装是面向对象程序设计的核心思想之一,它可以提高程序的可靠性、安全性、可维护性和可扩展性,是程序设计中不可或缺的重要概念。
当前设计问题
接口设计:定义初始化(Nixie_init),和具体功能(Nixie_display),初始化和芯片开发板设计相关,功能的定义和业务相关。
初始化问题:为什么不用库函数?首先是可以使用库函数的。观察使用库函数和不是库函数的区别。一个初始化写在头文件,一个写在c文件。c文件是实现,做到抛开平台相关是最好的方案,也就是换了芯片平台,实现不动,通过改变头中的配置,就可以做到移植。(当然,理想状态是这样的,还得看实现复杂度。目标明确,尽量做到这个,为移植提供最少变化方案,这个是共识)
自定义码表

| 索引 | 显示值 | 导通管脚 | 共阳 | 共阴 | ||
|---|---|---|---|---|---|---|
| 二进制 | 16进制 | 二进制 | 16进制 | |||
| 0 | 0 | ,,F,E,D,C,B,A | 1100 0000 | 0xC0 | 0011 1111 | 0x3F |
| 1 | 1 | , ,,,,C,B, | 1111 1001 | 0xF9 | ||
| 2 | 2 | ,G,,E,D,,B,A | 1010 0100 | 0xA4 | ||
| 3 | 3 | ,G,,,D,C,B,A | 1011 0000 | 0xB0 | ||
| 4 | 4 | ,G,F,,,C,B, | 1001 1001 | 0x99 | ||
| 5 | 5 | ,G,F,,D,C,,A | 1001 0010 | 0x92 | ||
| 6 | 6 | ,G,F,E,D,C,,A | 1000 0010 | 0x82 | ||
| 7 | 7 | ,,,,,C,B,A | 1111 1000 | 0xF8 | ||
| 8 | 8 | ,G,F,E,D,C,B,A | 1000 0000 | 0x80 | ||
| 9 | 9 | ,G,F,,D,C,B,A | 1001 0000 | 0x90 | ||
| 10 | 0. | DP,,F,E,D,C,B,A | 0100 0000 | 0x64 | ||
| 11 | 1. | DP , ,,,,C,B, | 0111 1001 | 0x79 | ||
| 12 | 2. | DP,G,,E,D,,B,A | 0010 0100 | 0x24 | ||
| 13 | 3. | DP,G,,,D,C,B,A | 0011 0000 | 0x30 | ||
| 14 | 4. | DP,G,F,,,C,B, | 0001 1001 | 0x19 | ||
| 15 | 5. | DP,G,F,,D,C,,A | 0001 0010 | 0x12 | ||
| 16 | 6. | DP,G,F,E,D,C,,A | 0000 0010 | 0x02 | ||
| 17 | 7. | DP,,,,,C,B,A | 0111 1000 | 0x78 | ||
| 18 | 8. | DP,G,F,E,D,C,B,A | 0000 0000 | 0x00 | ||
| 19 | 9. | DP,G,F,,D,C,B,A | 0001 0000 | 0x10 | ||
| 20 | . | DP,,,,,,, | 0111 1111 | 0x7F | ||
| 21 | - | ,G,,,,,, | 1011 1111 | 0xBF | ||
| 22 | A | ,G,F,E,,C,B,A | 1000 1000 | 0x88 | ||
| 23 | b | ,G,F,E,D,C,, | 1000 0011 | 0x83 | ||
| 24 | C | ,,F,E,D,,,A | 1100 0110 | 0xC6 | ||
| 25 | d | ,G,,E,D,C,B, | 1010 0001 | 0xA1 | ||
| 26 | E | ,G,F,E,D,,,A | 1000 0110 | 0x86 | ||
| 27 | F | ,G,F,E,,,,A | 1000 1110 | 0x8E | ||
| 28 | H | ,G,F,E,,C,B, | 1000 1001 | 0x89 | ||
| 29 | J | ,,,,D,C,B, | 1111 0001 | 0xF1 | ||
| 30 | L | ,,F,E,D,,, | 1100 0111 | 0xC7 | ||
| 31 | P | ,G,F,E,,,B,A | 1000 1100 | 0x8C | ||
| 32 | q | ,G,F,,,C,B,A | 1001 1000 | 0x98 | ||
| 33 | U | ,,F,E,D,C,B, | 1100 0001 | 0xC1 |
// 索引对应表格参见:// https://www.yuque.com/icheima/stc8h/kmz2mllvxs1uvdfy#lLhhpu8 code LED_TABLE[] ={// 0 1 2 -> 9 (索引012...9)0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,// 0 1. 2. -> 9. (索引10,11,12....19)0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,// . - (索引20,21)0x7F, 0xBF,// AbCdEFHJLPqU (索引22,23,24....33)0x88,0x83,0xC6,0xA1,0x86,0x8E,0x89,0xF1,0xC7,0x8C,0x98,0xC1};// 每次只显示一个// \arg num 对应数字在数组里的位置(索引)// \arg id 显示在指定位置(0 -> 7)void NIXIE_display(u8 num, u8 id){u8 a_dat = LED_TABLE[num]; // 0001 0010 字母位u8 b_idx = 1 << id; // 0010 0000 数字位 5NIXIE_show(a_dat, b_idx);}
- index在没有封装前,一个bit表示一个灯。封装后表示灯的下标。这样设计符合人的思考习惯,函数就是让人调得舒服
- dat在没有封装前,是自己来总结灯的开灭,封装后表示自己定义的字符,通过下标访问。这样简化操作。还是为了调用舒服。
数字走马灯实现
#include "Config.h"#include "NIXIE.h"#include "Delay.h"int main() {u8 i;// u8 a_dat = 0xF8; // 0001 0010 字母位// u8 b_idx = 0xFB; // 0001 1111 数字位NIXIE_init();// NIXIE_show写到循环外边即可// NIXIE_show(a_dat, b_idx);while(1) {// 1000 / (2 * 8) = 62.5帧/秒// 走马灯for(i = 0; i < 8; i++) {NIXIE_display(i + 1, i);delay_ms(100);}}}

以上是芯片手册的管脚定义。
- 10号引脚:SCLR,移位寄存器清零端。
- 13号引脚:G,输出使能端。

以上是手册中的真值表。
我们的原理图中 10号引脚(SCLR)为 VCC 高电平,13号引脚(G)为GND 低电平。对照以上表,我们进行观察。
- 如果有需求,我们可以控制
SCLR引脚进行寄存器的清理,当然需要开发板中进行引脚设计。 G引脚我们设置为GND(即低电平),如果配置为高电平,则不再亮灯输出。串行输入并行输出
其实我们的 74hc595就是串行输入数据,然后并行输出信号的芯片。并行输入串行输出
其实也有种需求,模拟信号需要变为二进制数据,那这种就需要并行输入串行输出。和74hc595正好相反。
目前常见的芯片有74HC165。协议解析方式按照芯片手册来,通常和74hc595相反。
练习题
- 实现走马灯效果
- 通过串口控制显示
- 通过独立按键进行数码管显示控制

