学习目标

  1. 理解自定义码表
  2. 数码管驱动封装
  3. 掌握数字走马灯实现

    学习内容

    驱动封装

    根据前面的内容可以将代码进行封装,封装后作为一个独立的整体出现: ```c

    ifndef NIXIE_H

    define NIXIE_H

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

  1. - 定义 `NIXIE_init()` 函数,负责对GPIO相关的初始化
  2. - 定义 `NIXIE_display()`函数负责显示
  3. ```c
  4. #include "NIXIE.h"
  5. #include "Delay.h"
  6. #define GET_BIT_VAL(byte, pos) (byte & (1 << pos))
  7. //#define NOP_TIME() NOP40() // 用于看logic分析仪
  8. #define NOP_TIME() NOP2()
  9. // 锁存操作 - 多行宏定义
  10. #define RCK_ACTION() \
  11. NIXIE_RCK = 0; \
  12. NOP_TIME(); \
  13. NIXIE_RCK = 1; \
  14. NOP_TIME();
  15. void NIXIE_init(){
  16. NIXIE_PIN_INIT();
  17. }
  18. static void NIXIE_out(u8 dat){
  19. char i;
  20. // 8bit,先发出去的会作为高位
  21. for(i = 7; i >= 0; i--){
  22. NIXIE_DI = GET_BIT_VAL(dat, i);
  23. // 寄存器的移位操作
  24. NIXIE_SCK = 0;
  25. NOP_TIME(); // 休眠一会儿
  26. NIXIE_SCK = 1;
  27. NOP_TIME(); // 休眠一会儿
  28. }
  29. }
  30. // 每次可以显示多个,但是内容都是一样的a_dat
  31. // u8 a_dat = 0x12; // 0001 0010 字母位
  32. // u8 b_idx = 0x1F; // 0001 1111 数字位
  33. void NIXIE_show(u8 a_dat, u8 b_idx){
  34. // 显示 7.
  35. // 0111 1000
  36. // 先发字母位 (控制显示的内容) // 0点亮
  37. // 8bit,先发出去的会作为高位
  38. NIXIE_out(a_dat);
  39. // 0,1,2,3....7
  40. // 再发数字位 (控制显示哪几个) // 只要不是0,就是高电平
  41. // 1111 1011
  42. // 7.7.空7. 7.7.7.7. -------------------与二级制是反向
  43. // 8bit,先发出去的会作为高位
  44. NIXIE_out(b_idx);
  45. // 锁存操作
  46. RCK_ACTION();
  47. }

以上为Nixie.h的实现,也是对之前代码的封装处理。

  1. #include "Config.h"
  2. #include "NIXIE.h"
  3. #include "Delay.h"
  4. int main() {
  5. u8 a_dat = 0xF8; // 0001 0010 字母位
  6. u8 b_idx = 0xFB; // 0001 1111 数字位
  7. NIXIE_init();
  8. // NIXIE_show写到循环外边即可
  9. NIXIE_show(a_dat, b_idx);
  10. while(1) {
  11. }
  12. }

以上为 main.c中使用我们封装的驱动。以上代码就会很简洁。

封装的一些疑问

封装的特点

封装是面向对象程序设计中的一个重要概念,它将数据和行为封装在一起,形成一个独立的单元。下面是封装的特点:

  1. 数据隐藏:封装可以隐藏数据,只对外界公开必要的接口,从而保证数据的安全性和可靠性。
  2. 接口统一:封装可以将数据和行为组织在一起,形成一个类或对象,通过统一的接口对外提供服务,便于使用和管理。
  3. 信息隐藏:封装可以隐藏实现细节,只对外界公开必要的信息,从而降低了程序的复杂度和耦合度,提高了程序的可维护性和可扩展性。
  4. 可重用性:封装可以将数据和行为封装成一个独立的单元,便于复用和重复利用,提高了程序的开发效率和代码的可重用性。
  5. 封装和继承相结合:封装和继承是面向对象程序设计中的两个重要概念,它们相互配合,可以构建出更加复杂、灵活和可扩展的程序。

总之,封装是面向对象程序设计的核心思想之一,它可以提高程序的可靠性、安全性、可维护性和可扩展性,是程序设计中不可或缺的重要概念。

当前设计问题

接口设计:定义初始化(Nixie_init),和具体功能(Nixie_display),初始化和芯片开发板设计相关,功能的定义和业务相关。
初始化问题:为什么不用库函数?首先是可以使用库函数的。观察使用库函数和不是库函数的区别。一个初始化写在头文件,一个写在c文件。c文件是实现,做到抛开平台相关是最好的方案,也就是换了芯片平台,实现不动,通过改变头中的配置,就可以做到移植。(当然,理想状态是这样的,还得看实现复杂度。目标明确,尽量做到这个,为移植提供最少变化方案,这个是共识)

自定义码表

微信截图_20221128105958.png

索引 显示值 导通管脚 共阳 共阴
二进制 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
  1. // 索引对应表格参见:
  2. // https://www.yuque.com/icheima/stc8h/kmz2mllvxs1uvdfy#lLhhp
  3. u8 code LED_TABLE[] =
  4. {
  5. // 0 1 2 -> 9 (索引012...9)
  6. 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,
  7. // 0 1. 2. -> 9. (索引10,11,12....19)
  8. 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,
  9. // . - (索引20,21)
  10. 0x7F, 0xBF,
  11. // AbCdEFHJLPqU (索引22,23,24....33)
  12. 0x88,0x83,0xC6,0xA1,0x86,0x8E,0x89,0xF1,0xC7,0x8C,0x98,0xC1
  13. };
  14. // 每次只显示一个
  15. // \arg num 对应数字在数组里的位置(索引)
  16. // \arg id 显示在指定位置(0 -> 7)
  17. void NIXIE_display(u8 num, u8 id){
  18. u8 a_dat = LED_TABLE[num]; // 0001 0010 字母位
  19. u8 b_idx = 1 << id; // 0010 0000 数字位 5
  20. NIXIE_show(a_dat, b_idx);
  21. }
  • index在没有封装前,一个bit表示一个灯。封装后表示灯的下标。这样设计符合人的思考习惯,函数就是让人调得舒服
  • dat在没有封装前,是自己来总结灯的开灭,封装后表示自己定义的字符,通过下标访问。这样简化操作。还是为了调用舒服。

数字走马灯实现

  1. #include "Config.h"
  2. #include "NIXIE.h"
  3. #include "Delay.h"
  4. int main() {
  5. u8 i;
  6. // u8 a_dat = 0xF8; // 0001 0010 字母位
  7. // u8 b_idx = 0xFB; // 0001 1111 数字位
  8. NIXIE_init();
  9. // NIXIE_show写到循环外边即可
  10. // NIXIE_show(a_dat, b_idx);
  11. while(1) {
  12. // 1000 / (2 * 8) = 62.5帧/秒
  13. // 走马灯
  14. for(i = 0; i < 8; i++) {
  15. NIXIE_display(i + 1, i);
  16. delay_ms(100);
  17. }
  18. }
  19. }
  • 调整帧率来控制显示,达到走马灯的效果

    扩展知识

    image.png
    原理图中,大部分引脚我们已经知道了,但是还有些引脚功能不清楚。

  • RESET: 10号引脚。

  • OUTPUT_ENABLE: 13号引脚。

68.png
以上是芯片手册的管脚定义。

  • 10号引脚:SCLR,移位寄存器清零端。
  • 13号引脚:G,输出使能端。

69.png
以上是手册中的真值表。
我们的原理图中 10号引脚(SCLR)为 VCC 高电平,13号引脚(G)为GND 低电平。对照以上表,我们进行观察。

  1. 如果有需求,我们可以控制SCLR引脚进行寄存器的清理,当然需要开发板中进行引脚设计。
  2. G引脚我们设置为GND(即低电平),如果配置为高电平,则不再亮灯输出。

    串行输入并行输出

    其实我们的 74hc595就是串行输入数据,然后并行输出信号的芯片。

    并行输入串行输出

    其实也有种需求,模拟信号需要变为二进制数据,那这种就需要并行输入串行输出。和74hc595正好相反。
    目前常见的芯片有74HC165。协议解析方式按照芯片手册来,通常和74hc595相反。

练习题

  1. 实现走马灯效果
  2. 通过串口控制显示
  3. 通过独立按键进行数码管显示控制