段码屏开发—GPT4o带来的一点点震撼

硬件简介

屏幕

屏幕使用了一块微雪的电子墨水段码屏

文档在这里:
https://www.waveshare.net/wiki/1.9inch_Segment_e-Paper_Module_Manual

MCU

MCU方面就是树莓派的PICO,只不过是物理体积更小的版本,用法是一模一样的

https://www.waveshare.net/wiki/RP2040-Zero

字节(byte)和比特(bit)的对应

最近买了块高颜值段码屏,电子墨水屏原理的,一直没弄明白这个文档里说得是个啥意思,恰好最近尝试使用GPT来解决我遇到的一些编程问题,
本来只是抱着试试看的态度,因为自从chatgpt火了以后一直持观望态度,印象中还只是能给它设定模仿人格写写小说一类的,然而这次实在小震撼到我
起初这个表对于完全没接触过这块的我完全不动他是什么意思

疑惑的关键在于0x00怎么就全0,而0xff怎么就全1了呢,于是我问GPT:

*以下是GPT的回答
在这个图表中,每个字节(byte)由 8 个比特(bit)组成,且每个比特都可以存储一个二进制值(0 或 1)。这些比特在图像中对应的像素(pixel)上,决定了每个像素的颜色(例如,黑色或白色)。

理解图表中的内容

  1. 像素 (pixel): 从 1 到 16 的编号表示每个像素的位置。
  2. 比特 (bit): 每个像素位置对应一个比特,从 7 到 0 代表一个字节中的每一位。
  3. 存储的数据: 存储在比特中的数据值(0 或 1)。
  4. 对应的颜色: 由数据值决定的像素颜色(黑色或白色)。
    5。 字节 (byte): 0x00 和 0xff 表示字节的十六进制值。

    字节和比特的对应关系

  • 一个字节有 8 位,每一位可以是 0 或 1。
  • 0x00 表示 8 个比特全部为 0,转换为二进制是 00000000。
  • 0xff 表示 8 个比特全部为 1,转换为二进制是 11111111。

    解释示例

  • 0x00 字节:
    • 二进制表示:00000000
    • 每个比特位都是 0,因此对应的像素颜色是黑色。
    • 从像素 1 到 8 的存储数据都是 0,所以对应颜色都是黑色。
  • 0xff 字节:
    • 二进制表示:11111111
    • 每个比特位都是 1,因此对应的像素颜色是白色。
    • 从像素 9 到 16 的存储数据都是 1,所以对应颜色都是白色。

      示例代码解释

      如果你想用代码来解释字节和比特之间的关系,可以如下示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      # 示例字节
      byte_00 = 0x00 # 所有比特位都是 0
      byte_ff = 0xff # 所有比特位都是 1

      # 打印二进制表示
      print(f"0x00 的二进制表示: {bin(byte_00)[2:].zfill(8)}")
      print(f"0xff 的二进制表示: {bin(byte_ff)[2:].zfill(8)}")

      # 转换为像素颜色
      pixels_00 = ["■" if bit == '0' else "□" for bit in bin(byte_00)[2:].zfill(8)]
      pixels_ff = ["■" if bit == '0' else "□" for bit in bin(byte_ff)[2:].zfill(8)]

      print(f"0x00 对应的像素颜色: {''.join(pixels_00)}")
      print(f"0xff 对应的像素颜色: {''.join(pixels_ff)}")

      输出

      1
      2
      3
      4
      5
      复制代码
      0x00 的二进制表示: 00000000
      0xff 的二进制表示: 11111111
      0x00 对应的像素颜色: ■■■■■■■■
      0xff 对应的像素颜色: □□□□□□□□
      在这里,■ 表示黑色像素,□ 表示白色像素。这个代码示例展示了如何根据字节中的比特值确定像素颜色。

天呐!有理有据条例清晰,秒懂了,原来byte是十六进制要转为二进制的!而8位二进制就可以表示8个数码管的开与关了!

数字显示与byte的对应

*GPT回答:
为了将数字 23.8 和 54.9 表示为 DSPNUM_1in9_off 数组中的十六进制值,我们需要将每一位数字对应的十六进制编码放入数组的正确位置。根据你提供的映射:

  • 0 :0xbf, 0x1f

  • 1 :0x00, 0x1f

  • 2 :0xfd, 0x17

  • 3 :0xf5, 0x1f

  • 4 :0x47, 0x1f

  • 5 :0xf7, 0x1d

  • 6 :0xff, 0x1d

  • 7 :0x21, 0x1f

  • 8 :0xff, 0x1f

  • 9 :0xf7, 0x1f
    根据你的要求:

  • 数字 A 为 23.8

    • 十位 2 对应 DSPNUM_1in9_off[1] 和 DSPNUM_1in9_off[2]
    • 个位 3 对应 DSPNUM_1in9_off[3] 和 DSPNUM_1in9_off[4]
    • 小数点后一位 8 对应 DSPNUM_1in9_off[11] 和 DSPNUM_1in9_off[12]
  • 数字 B 为 54.9

    • 十位 5 对应 DSPNUM_1in9_off[5] 和 DSPNUM_1in9_off[6]
    • 个位 4 对应 DSPNUM_1in9_off[7] 和 DSPNUM_1in9_off[8]
    • 小数点后一位 9 对应 DSPNUM_1in9_off[9] 和 DSPNUM_1in9_off[10]

替换数组

将这些值替换到数组 DSPNUM_1in9_off 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned char DSPNUM_1in9_off[] = {
0x00, // [0] 保持为 0x00
0xfd, // [1] 数字 A 的十位 2 对应
0x17, // [2] 数字 A 的十位 2 对应
0xf5, // [3] 数字 A 的个位 3 对应
0x1f, // [4] 数字 A 的个位 3 对应
0xf7, // [5] 数字 B 的十位 5 对应
0x1d, // [6] 数字 B 的十位 5 对应
0x47, // [7] 数字 B 的个位 4 对应
0x1f, // [8] 数字 B 的个位 4 对应
0xf7, // [9] 数字 B 的小数点后一位 9 对应
0x1f, // [10] 数字 B 的小数点后一位 9 对应
0xff, // [11] 数字 A 的小数点后一位 8 对应
0x1f, // [12] 数字 A 的小数点后一位 8 对应
0x00, // [13] 保持为 0x00
0x00, // [14] 保持为 0x00
0x00 // [15] 保持为 0x00
};

最终数组

1
2
3
unsigned char DSPNUM_1in9_off[] = {
0x00, 0xfd, 0x17, 0xf5, 0x1f, 0xf7, 0x1d, 0x47, 0x1f, 0xf7, 0x1f, 0xff, 0x1f, 0x00, 0x00, 0x00
};

这个数组 DSPNUM_1in9_off 现在包含了你要求的数字 A 和 B 的十六进制表示形式。

GPT准确无误的理解了我的意思,这是这段代码直接得到的结果!!:

动态将数字转换为对应的段码

上面一段只是实现了数字到段码的转换,作为一个温度计肯定需要每隔一段时间就将读取的温度传到这个数组里,所以我继续问

GPT又准确无误的给了我方案:

动态调整数组的代码

为了动态调整 DSPNUM_1in9_off 数组,你可以编写一个函数,根据给定的 A 和 B 数值,动态设置数组的值。这个函数可以使用一个查找表来将数字映射到相应的字节值。

以下是一个实现示例:

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
#include <stdio.h>

// 定义数字对应的字节数组
unsigned char digit_bytes[10][2] = {
{0xbf, 0x1f}, // 0
{0x00, 0x1f}, // 1
{0xfd, 0x17}, // 2
{0xf5, 0x1f}, // 3
{0x47, 0x1f}, // 4
{0xf7, 0x1d}, // 5
{0xff, 0x1d}, // 6
{0x21, 0x1f}, // 7
{0xff, 0x1f}, // 8
{0xf7, 0x1f} // 9
};

// 定义DSPNUM_1in9_off数组
unsigned char DSPNUM_1in9_off[16] = {0};

// 更新数组的函数
void update_display(double A, double B) {
// 提取 A 和 B 的各位数字
int A_tens = (int)(A / 10) % 10;
int A_units = (int)A % 10;
int A_decimal = (int)(A * 10) % 10;

int B_tens = (int)(B / 10) % 10;
int B_units = (int)B % 10;
int B_decimal = (int)(B * 10) % 10;

// 设置数字 A
DSPNUM_1in9_off[1] = digit_bytes[A_tens][0];
DSPNUM_1in9_off[2] = digit_bytes[A_tens][1];
DSPNUM_1in9_off[3] = digit_bytes[A_units][0];
DSPNUM_1in9_off[4] = digit_bytes[A_units][1];
DSPNUM_1in9_off[11] = digit_bytes[A_decimal][0];
DSPNUM_1in9_off[12] = digit_bytes[A_decimal][1];

// 设置数字 B
DSPNUM_1in9_off[5] = digit_bytes[B_tens][0];
DSPNUM_1in9_off[6] = digit_bytes[B_tens][1];
DSPNUM_1in9_off[7] = digit_bytes[B_units][0];
DSPNUM_1in9_off[8] = digit_bytes[B_units][1];
DSPNUM_1in9_off[9] = digit_bytes[B_decimal][0];
DSPNUM_1in9_off[10] = digit_bytes[B_decimal][1];
}

int main() {
double A = 23.8;
double B = 54.9;

// 更新显示
update_display(A, B);

// 打印数组内容以验证
for (int i = 0; i < 16; i++) {
printf("0x%02x ", DSPNUM_1in9_off[i]);
}
printf("\n");

return 0;
}

解释

  1. 数字到字节的映射表:

    • digit_bytes 数组包含了每个数字(0 到 9)对应的两个字节的值。
  2. 提取数字位:

    • 使用整数除法和取模运算提取 A 和 B 的十位、个位和小数点后一位。
  3. 更新显示数组:

    • 使用提取到的数字位将 DSPNUM_1in9_off 数组的相应位置更新为查找表中的字节值。
  4. 主函数:

    • 定义 A 和 B 的值,调用 update_display 函数更新显示数组,并打印数组内容以验证。

这样,DSPNUM_1in9_off 数组将根据传入的 A 和 B 值动态更新。

小数点的处理

上面已经完整实现了两个数字到数组内的转换,但是转出来没有小数点,根据文档:

所以要在数组的第4,8,10位做些处理

我继续对GPT说

GPT回答:

为了实现这个功能,我们需要将特定数组位置的十六进制数转换为二进制,修改二进制数中的特定位,然后将其转换回十六进制并存储在数组中。

以下是修改后的代码,包括处理 DSPNUM_1in9_off[4], DSPNUM_1in9_off[8] 和 DSPNUM_1in9_off[10]:

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
#include <stdio.h>

// 定义数字对应的字节数组
unsigned char digit_bytes[10][2] = {
{0xbf, 0x1f}, // 0
{0x00, 0x1f}, // 1
{0xfd, 0x17}, // 2
{0xf5, 0x1f}, // 3
{0x47, 0x1f}, // 4
{0xf7, 0x1d}, // 5
{0xff, 0x1d}, // 6
{0x21, 0x1f}, // 7
{0xff, 0x1f}, // 8
{0xf7, 0x1f} // 9
};

// 定义DSPNUM_1in9_off数组
unsigned char DSPNUM_1in9_off[16] = {0};

// 将第六位从右往左数改成1的函数
unsigned char set_sixth_bit_to_one(unsigned char value) {
return value | (1 << 5); // 第六位对应的二进制位是 5,从0开始计数
}

// 更新数组的函数
void update_display(double A, double B) {
// 提取 A 和 B 的各位数字
int A_tens = (int)(A / 10) % 10;
int A_units = (int)A % 10;
int A_decimal = (int)(A * 10) % 10;

int B_tens = (int)(B / 10) % 10;
int B_units = (int)B % 10;
int B_decimal = (int)(B * 10) % 10;

// 设置数字 A
DSPNUM_1in9_off[1] = digit_bytes[A_tens][0];
DSPNUM_1in9_off[2] = digit_bytes[A_tens][1];
DSPNUM_1in9_off[3] = digit_bytes[A_units][0];
DSPNUM_1in9_off[4] = set_sixth_bit_to_one(digit_bytes[A_units][1]); // 修改第六位
DSPNUM_1in9_off[11] = digit_bytes[A_decimal][0];
DSPNUM_1in9_off[12] = digit_bytes[A_decimal][1];

// 设置数字 B
DSPNUM_1in9_off[5] = digit_bytes[B_tens][0];
DSPNUM_1in9_off[6] = digit_bytes[B_tens][1];
DSPNUM_1in9_off[7] = digit_bytes[B_units][0];
DSPNUM_1in9_off[8] = set_sixth_bit_to_one(digit_bytes[B_units][1]); // 修改第六位
DSPNUM_1in9_off[9] = digit_bytes[B_decimal][0];
DSPNUM_1in9_off[10] = set_sixth_bit_to_one(digit_bytes[B_decimal][1]); // 修改第六位
}

int main() {
double A = 23.8;
double B = 54.9;

// 更新显示
update_display(A, B);

// 打印数组内容以验证
for (int i = 0; i < 16; i++) {
printf("0x%02x ", DSPNUM_1in9_off[i]);
}
printf("\n");

return 0;
}

解释

  1. 函数 set_sixth_bit_to_one:

    • 使用位运算将一个字节的第六位(从右数)设置为 1。
  2. 更新 DSPNUM_1in9_off 数组:

    • 按照你的要求更新数字 A 和 B 的各个位数字。
    • 在设置 DSPNUM_1in9_off[4], DSPNUM_1in9_off[8] 和 DSPNUM_1in9_off[10] 时,调用 set_sixth_bit_to_one 函数将其第六位设置为 1。

      结果

      运行代码后,DSPNUM_1in9_off 数组将根据 A 和 B 的值动态更新,并且 DSPNUM_1in9_off[4], DSPNUM_1in9_off[8] 和 DSPNUM_1in9_off[10] 的第六位将被设置为 1。

然后我就得到了:

这里数字是我手动指定的A=73.6,B=84.9,可以看到小数点,百分号都已经显示出来了

再接入传感器读到的数值,温度计就差不多完工啦:

顺便一提这里的传感器驱动代码也是GPT帮调整的,真是神奇