# VFD荧光显示屏模块手册

真空荧光显示屏(Vacuum Fluorescent Display,简称VFD)是20世纪60年代发明的一种自发光平板显示器,其特有高亮度、广视角、耐环境等优点。 外部结构是玻璃材质,内部置有灯丝(直热式氧化物阴极)、栅极(栅网)以及阳极(涂覆有显示图形的荧光粉的导体)等基本电极。

# 1. 产品说明

VFD时钟具有9位显示内容,可显示数字和字母 驱动芯片是PT6315,通讯协议SPI。 DIN、CLK、RST三线组成 负压产生约为-35V,50%占空比的交流灯丝驱动,显示清晰。 外部供电需 5V供电,不可超过5V标准。 电路中请保证供电电流,防止下压降导致其他电路或单片机无法正常运行,通讯电平3.3V。

# 1.1 引脚说明

  • DIN: 数据输入引脚
  • CLK: 时钟输入引脚
  • STB: 串行接口选通管脚
  • EN^: 供电使能(高有效,低电平关断)默认上拉
  • +5V: 5V电压输入
  • GND: 电源地

SCH_Schematic1_1-P1_2023-12-25

pcb_size

PCB四角打孔尺寸M2

# > 其他说明

项目全开源,已在立创开源社区发布: https://oshwhub.com/yc_chen/vfd-xian-shi-mo-kuai-san-hnv

STC8H驱动的示例项目:https://github.com/ccy-studio/CCY-HNVM-TEST

# 驱动代码适用于单片机全平台

玩本模块相信你需要具备简单的入门基础,如果入门基础都不具备请先去学习对应的知识。 这里不提供太基础的技术支持。例如翻转IO,初始化IO、延时函数不知道怎么写这类问题。不提供技术支持,因为这些太太太基础了,请自行百度学习。

本驱动库代码,您可以直接在您的项目中创建对应的文件,然后直接复制过去,请注意编码格式。

之后您需要修改下对应的宏定义,即可完成全部适配。

驱动库文件分别拆分成两份封装API,其中以底层通讯pt6315,另一个则可以理解成应用层操作显示内容库API。

首先根据您的平台例如 C51、Arduino系列、STM32等等 修改初始化IO和延时函数的宏定义。

例如: C51的延时采用软件延时

void delay_us(u32 us) {  //c51单片机的软件延时,这里只是实例不要复制
    unsigned char data i;

    do {
        _nop_();
        i = 5;
        while (--i)
            ;
    } while (--us);
}
1
2
3
4
5
6
7
8
9
10

Arduino系列的微秒延时

delayMicroseconds(1000)	//以微秒为单位时间,延时1000微秒,即1毫秒
1

STM32的微秒延时可以软件或者定时器,这里就不做多的介绍了。

后面就是操作IO的部分宏定义,如果是C51那么可以是 P31=1类似这种,Arduino的可以使用 digitalWrite(pin, value) 来操作IO引脚的电平翻转。 请根据您的平台去做特定的修改。下面的代码部分将会体现。

# 1. 创建文件: pt6315.h

#ifndef __PT6315__
#define __PT6315__

#define CLK_1 P_CLK = 1
#define CLK_0 P_CLK = 0
#define DIN_1 P_DIN = 1
#define DIN_0 P_DIN = 0
#define STB_1 P_STB = 1
#define STB_0 P_STB = 0

#define delay_us(x) my_delay_us(x)
#define delay_ms(x) my_delay_ms(x)

void ptSetDisplayLight(uint8_t onOff, uint8_t brightnessVal);
void setModeWirteDisplayMode(uint8_t addressMode);
void setDisplayMode(uint8_t digit);
void sendDigAndData(uint8_t dig, uint8_t* dat, size_t len);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

在宏定义这里CLK_1 、CLK_0 等等 三个IO引脚的宏定义需要您根据自己的平台去编写IO的翻转,上图的示例是C51的

假如您的平台是Arduino那么需要修改代码例如:

#define CLK_1 digitalWrite(CLK,HIGH) //高电平输出
#define CLK_0 digitalWrite(CLK,LOW) //低电平输出
...
...
1
2
3
4

注意在使用前需要在main方法内一定要先初始化IO引脚,初始化IO引脚,初始化IO引脚,,例如Arduino系列操作**pinMode()**函数。

修改宏定义的两个延时函数:delay_us、delay_ms ,例如还是Arduino系列的则可以修改为

注意如果是Arduino需要引入头文件: #include <Arduino.h> 其他平台也类似。

#define delay_us(x) delayMicroseconds(x) //微秒延时
#define delay_ms(x) delay(x) //毫秒延时
1
2

在复制下面的代码前,需要先确认您修改完成了上面的宏定义的方法。

# 2. 创建文件: pt6315.c

#include "pt6315.h"

void writeData(uint8_t dat) {
    size_t i = 0;
    CLK_0;
    for (i = 0; i < 8; i++) {
        delay_us(10);
        if (dat & 0x01) {
            DIN_1;
        } else {
            DIN_0;
        }
        delay_us(10);
        CLK_1;
        delay_us(10);
        CLK_0;
        dat >>= 1;
    }
}

void setModeWirteDisplayMode(uint8_t addressMode) {
    uint8_t command = 0x40;
    if (addressMode) {
        command |= 0x4;
    }
    STB_1;
    delay_us(10);
    STB_0;
    delay_us(10);
    writeData(command);
    delay_us(10);
    STB_1;
}

/**
 * COMMANDS 1 显示模式设置命令
 * 0000 : 4位, 24段
 * 0001 :5位, 23段
 * 0010 : 6位数字, 22段
 * 0011 : 7位, 21段
 * 0100 : 8位, 20段
 * 0101 : 9位, 19段
 * 0110 : 10位, 18段
 * 0111 : 11位, 17段
 * 1XXX : 12位, 16段
 */
void setDisplayMode(uint8_t digit) {
    STB_1;
    delay_us(10);
    STB_0;
    delay_us(10);
    writeData(digit);
    delay_us(10);
    STB_1;
}
/**
 * 显示控制命令  COMMANDS 4
 * @param onOff 0显示关闭,1开启显示
 * @param brightnessVal 亮度占空比 0~7调整
 * 000:脉冲宽度= 1/16 0
 * 001:脉冲宽度= 2/16 1
 * 010 :脉冲宽度= 4/16 0x2
 * 011 :脉冲宽度= 10/16 3
 * 100:脉冲宽度= 11/16  4
 * 101 :脉冲宽度= 12/16 0x5
 * 110:脉冲宽度= 13/16 6
 * 111:脉冲宽度= 14/16 0x7
 */
void ptSetDisplayLight(uint8_t onOff, uint8_t brightnessVal) {
    uint8_t command = 0x80 | brightnessVal;
    if (onOff) {
        command |= 0x8;
    }
    STB_1;
    delay_us(10);
    STB_0;
    delay_us(10);
    writeData(command);
    delay_us(10);
    STB_1;
}

void sendDigAndData(uint8_t dig, const uint8_t* dat, size_t len) {
    size_t i = 0;
    STB_1;
    delay_us(10);
    STB_0;
    delay_us(10);
    writeData(0xc0 | dig);
    delay_us(10);
    for (i = 0; i < len; i++) {
        writeData(dat[i]);
    }
    delay_us(10);
    STB_1;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

# 3. 创建文件:gui.h

#ifndef __VFD_GUI_
#define __VFD_GUI_

#include "pt6315.h"

// VFD位数
#define VFD_DIG_LEN 9

/**
 * 初始化
 */
void vfd_gui_init();

/**
 * 停止关闭显示
 */
void vfd_gui_stop();

/**
 * 清空VFD屏幕显示,循环刷新如果使用vfd_gui_set_text方则不需要使用它。
 */
void vfd_gui_clear();

/**
 * 显示一串文字,从0位开始。
 * (自动清空覆盖显示,方便每次不用调用clear防止闪屏出现)
 * @param colon 是否显示冒号 0|1
 * @param left_first_conlon 是否显示左边冒号 0|1
 */
void vfd_gui_set_text(const char* string,
                      const u8 colon,
                      const u8 left_first_conlon);

/**
 * 要点亮的ICON图标,可宏定义传参可参考acg动画
 */
void vfd_gui_set_icon(u32 buf);

/**
 * 设置亮度等级 1~7
 */
void vfd_gui_set_blk_level(size_t level);

/**
 * acg动画
 */
void vfd_gui_acg_update();


long map(long x, long in_min, long in_max, long out_min, long out_max);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 4. 创建文件:gui.c

#include "gui.h"

u8 lightLevel = 1;  // 亮度级别
static u8 send_buf[3 * VFD_DIG_LEN] = {0};
static u8 send_buf_cache[3 * VFD_DIG_LEN] = {0};
const u32 fonts[38] = {
    0x333300,  // ASCII:0,ASCII_N:48 index:0
    0x201000,  // ASCII:1,ASCII_N:49 index:1
    0xe12100,  // ASCII:2,ASCII_N:50 index:2
    0xe13000,  // ASCII:3,ASCII_N:51 index:3
    0xe21000,  // ASCII:4,ASCII_N:52 index:4
    0xc33000,  // ASCII:5,ASCII_N:53 index:5
    0xc33100,  // ASCII:6,ASCII_N:54 index:6
    0x211000,  // ASCII:7,ASCII_N:55 index:7
    0xe33100,  // ASCII:8,ASCII_N:56 index:8
    0xe33000,  // ASCII:9,ASCII_N:57 index:9
    0x080400,  // ASCII::,ASCII_N:58 index:10
    0xe31100,  // ASCII:A,ASCII_N:65 index:11
    0xa93400,  // ASCII:B,ASCII_N:66 index:12
    0x032100,  // ASCII:C,ASCII_N:67 index:13
    0x293400,  // ASCII:D,ASCII_N:68 index:14
    0xc32100,  // ASCII:E,ASCII_N:69 index:15
    0xc30100,  // ASCII:F,ASCII_N:70 index:16
    0xc33100,  // ASCII:G,ASCII_N:71 index:17
    0xe21100,  // ASCII:H,ASCII_N:72 index:18
    0x092400,  // ASCII:I,ASCII_N:73 index:19
    0x082400,  // ASCII:J,ASCII_N:74 index:20
    0x520900,  // ASCII:K,ASCII_N:75 index:21
    0x022100,  // ASCII:L,ASCII_N:76 index:22
    0x361100,  // ASCII:M,ASCII_N:77 index:23
    0x261900,  // ASCII:N,ASCII_N:78 index:24
    0x233100,  // ASCII:O,ASCII_N:79 index:25
    0xe10100,  // ASCII:P,ASCII_N:80 index:26
    0x233900,  // ASCII:Q,ASCII_N:81 index:27
    0xe30900,  // ASCII:R,ASCII_N:82 index:28
    0xc33000,  // ASCII:S,ASCII_N:83 index:29
    0x090400,  // ASCII:T,ASCII_N:84 index:30
    0x223100,  // ASCII:U,ASCII_N:85 index:31
    0x241800,  // ASCII:V,ASCII_N:86 index:32
    0x221b00,  // ASCII:W,ASCII_N:87 index:33
    0x140a00,  // ASCII:X,ASCII_N:88 index:34
    0x140400,  // ASCII:Y,ASCII_N:89 index:35
    0x112004,  // ASCII:Z,ASCII_N:90 index:36
    0xc00000,  // ASCII:-,ASCII_N:45 index:37
};

u32 gui_get_font(char c);


void vfd_gui_init() {
    VFD_EN = 1;
   
    // VFD Setting
    setDisplayMode(6);
    setModeWirteDisplayMode(0);
    vfd_gui_set_blk_level(7);
    vfd_gui_clear();
}

void vfd_gui_stop() {
    VFD_EN = 0;
    vfd_gui_clear();
}

void vfd_gui_clear() {
    memset(send_buf, 0x00, sizeof(send_buf));
    sendDigAndData(0, send_buf, sizeof(send_buf));
}

void vfd_gui_set_icon(u32 buf) {
    uint8_t arr[3] = {0};
    memset(arr, 0x00, sizeof(arr));
    if (buf) {
        arr[0] = (buf >> 16) & 0xFF;
        arr[1] = (buf >> 8) & 0xFF;
        arr[2] = buf & 0xFF;
    }
    sendDigAndData(0x1b, arr, 3);
}

void vfd_gui_set_text(const char* string,
                      const u8 colon,
                      const u8 left_first_conlon) {
    size_t str_len = strlen(string);
    size_t index = 0, i = 0;
    size_t len = str_len > VFD_DIG_LEN ? VFD_DIG_LEN : str_len;
    memset(send_buf, 0x00, sizeof(send_buf));
    for (i = 0; i < len; i++) {
        if (string[i] && string[i] != '\0') {
            u32 buf = gui_get_font(string[i]);
            send_buf[index++] = (buf >> 16) & 0xFF;
            send_buf[index++] = (buf >> 8) & 0xFF;
            send_buf[index++] = buf & 0xFF;
        }
    }
    if (left_first_conlon) {
        send_buf[7] |= 0x40;
    }
    if (colon) {
        send_buf[13] |= 0x40;
        send_buf[19] |= 0x40;
    }
    sendDigAndData(0, send_buf, sizeof(send_buf));
}

/**
 * 亮度调节 1~7
 */
void vfd_gui_set_blk_level(size_t level) {
    if (level == lightLevel) {
        return;
    }
    lightLevel = level;
    ptSetDisplayLight(1, lightLevel);
}

long map(long x, long in_min, long in_max, long out_min, long out_max) {
    const long dividend = out_max - out_min;
    const long divisor = in_max - in_min;
    const long delta = x - in_min;
    return (delta * dividend + (divisor / 2)) / divisor + out_min;
}

u32 gui_get_font(char c) {
    if (c == ' ') {
        return 0;
    }
    if (c == '-') {
        return fonts[37];
    }
    if (c >= 48 && c <= 58) {
        return fonts[map(c, 48, 58, 0, 10)];
    } else if (c >= 65 && c <= 90) {
        return fonts[map(c, 65, 90, 11, 36)];
    } else if (c >= 97 && c <= 122) {
        return gui_get_font(c - 32);
    } else {
        return 0;
    }
}

/**
 * acg动画
 */
void vfd_gui_acg_update() {
    static u8 acf_i = 9;
    if (acf_i == 9) {
        static u32 icon = 0x040000;
        static u8 sec = 0;
        vfd_gui_set_icon(icon);
        sec++;
        if (sec == 3) {
            icon = 0x008000;
        } else if (sec < 3) {
            icon = (0x040000 >> sec);
        } else {
            icon = (0x040000 << (sec - 3));
        }
        if (sec == 4) {
            acf_i = 0;
        }
        if (sec == 7) {
            sec = 0;
            icon = 0x040000;
        }
    } else {
        u8 bi = acf_i == 0 ? 1 : (acf_i + 1) * 3 - 2;
        memcpy(send_buf_cache, send_buf, sizeof(send_buf));
        if (acf_i == 2 || acf_i == 4 || acf_i == 6) {
            send_buf_cache[bi] |= 0x80;
        } else {
            send_buf_cache[bi] |= 0xC0;
        }
        if (acf_i == 0) {
            vfd_gui_set_icon(0);
        }
        sendDigAndData(0, send_buf_cache, sizeof(send_buf_cache));
        acf_i++;
        if (acf_i == 9) {
            sendDigAndData(0, send_buf, sizeof(send_buf));
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

在代码中有一个宏定义:VFD_EN ,这个则是模块中EN引脚的电平翻转宏定义,这里可以根据您自己的平台做响应的修改。

例如Arduino系列的可以改为: digitalWrite(VFD_EN ,HIGH)、digitalWrite(VFD_EN ,LOW)

# 简单使用

#include "gui.h"
void main(){
    //初始化 DIN、CLK、RST、EN引脚IO为推挽输出模式
    ...
    ... 
    vfd_gui_init(); //初始化
    vfd_gui_set_blk_level(7); //亮度调整
    while(1){
        delay_ms(500);
        vfd_gui_set_text("123456789",0,0); //设置显示内容
        vfd_gui_acg_update();//动画帧刷新,如果使用RTOS需要对数据传输pt6315上锁
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13