<think>我们以数字"123"为例,详细解析在51单片机IIC总线OLED屏幕上显示数字时,`OLED_ShowNum`函数和`oled_pow`函数的逐位分离过程。
### 1. 函数调用
假设调用方式为:`OLED_ShowNum(0, 0, 123, 3, 16)`
- 起点坐标:`(x=0, y=0)`
- 显示数字:`num=123`
- 数字位数:`len=3`(显示3位)
- 字体大小:`sizey=16`(16×16点阵)
### 2. 逐位分离过程
函数内部处理流程如下:
```c
void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 sizey) {
u8 t, temp, m = 0;
u8 enshow = 0; // 前导零标志(0表示不显示前导零)
if (sizey == 8) m = 2; // 8号字体时额外间距调整(本例sizey=16,故m=0)
// 循环处理每一位(共3次循环)
for (t = 0; t < len; t++) {
// 步骤1:计算当前位权值(10的幂)
// len-t-1: 第一次循环为2(百位),第二次为1(十位),第三次为0(个位)
u32 pow_val = oled_pow(10, len-t-1); // 调用幂函数
// 步骤2:提取当前位数字(0-9)
temp = (num / pow_val) % 10;
// 步骤3:处理前导零(除最后一位外,高位为零时显示空格)
if (enshow == 0 && t < (len-1)) { // 未遇到非零且不是最后一位
if (temp == 0) {
// 显示空格(跳过当前位)
OLED_ShowChar(x + (sizey/2 + m)*t, y, ' ', sizey);
continue; // 跳过后续显示
} else {
enshow = 1; // 遇到非零,后续位全部显示
}
}
// 步骤4:显示当前位数字(数字转ASCII:temp+'0')
OLED_ShowChar(x + (sizey/2 + m)*t, y, temp+'0', sizey);
}
}
```
#### 具体分解(以"123"为例):
- **第1次循环(t=0,处理百位)**:
- `len-t-1 = 3-0-1 = 2` → 计算 `pow_val = oled_pow(10,2) = 100`
- `temp = (123/100) % 10 = 1 % 10 = 1`
- 前导零判断:`enshow=0`(初始)且 `t=0 < 2`(len-1=2),但 `temp=1 ≠0` → 设置 `enshow=1`
- 显示:调用 `OLED_ShowChar(0 + (16/2+0)*0, 0, '1', 16)` → 在坐标(0,0)显示'1'
- **第2次循环(t=1,处理十位)**:
- `len-t-1 = 3-1-1 = 1` → `pow_val = oled_pow(10,1)=10`
- `temp = (123/10) % 10 = 12 % 10 = 2`
- 前导零判断:`enshow=1`(已置位)→ 跳过判断
- 显示:调用 `OLED_ShowChar(0 + 8*1, 0, '2', 16)` → 在坐标(8,0)显示'2'
- **第3次循环(t=2,处理个位)**:
- `len-t-1=3-2-1=0` → `pow_val=oled_pow(10,0)=1`
- `temp=(123/1)%10=123%10=3`
- 前导零判断:`t=2`等于`len-1=2`(最后一位)→ 即使为零也显示
- 显示:调用 `OLED_ShowChar(0+8*2,0,'3',16)` → 在坐标(16,0)显示'3'
#### 显示效果:
```
坐标: (0,0) (8,0) (16,0)
显示: 1 2 3
```
### 3. 关键函数`oled_pow`解析
幂函数计算 $10^n$ 的值:
```c
u32 oled_pow(u8 m, u8 n) {
u32 result = 1;
while (n--) result *= m; // 循环n次,累乘m
return result;
}
```
- 示例:`oled_pow(10,2)`:
- 初始化:`result=1`
- 循环2次:
- 第1次:`n=2` → `result=1*10=10`,`n`减为1
- 第2次:`n=1` → `result=10*10=100`,`n`减为0
- 返回100
### 4. 底层字符显示`OLED_ShowChar`
对于每个数字字符(如'1'),其显示过程:
1. 计算字符在字库中的偏移量:`c = '1' - ' ' = 49 - 32 = 17`
2. 根据字体大小选择字模数据:
- 16×16字体:查表`asc2_1608[17]`(实际为16×8,需分两半显示)
3. 分两次写入(每8行一页):
- 先写上半部分8×8数据(页0)
- 再写下半部分8×8数据(页1)
> 注:具体字模数据存储在常量数组中,如引用[3]中描述的ASCII字库[^3]。
### 5. IIC通信底层
最终通过IIC协议发送数据到OLED:
1. 写命令设置光标位置(`OLED_Set_Pos`)
2. 循环发送字模数据(`OLED_WR_Byte`)
- 每次发送一个字节(8个像素点)
如引用[3]所示,IIC数据包格式为:
- 起始信号 → 从机地址(0x78) → 控制字节(0x00/0x40)→ 数据字节 → 停止信号[^3]。
***
§§相关问题§§
1. 如何修改代码以实现固定位数显示(如始终显示3位数字,不足补零)?
2. 若需要显示负数(如-123),应如何扩展当前函数?
3. 在低功耗应用中,如何优化数字显示以减少IIC通信次数?
</think>### 数字"123"在 OLED_ShowNum 函数中的逐位分离过程解析
以下以调用 `OLED_ShowNum(0, 0, 123, 3, 16)` 为例(起点坐标(0,0),显示数字123,3位数,16×16字体),详细展示逐位分离过程:
#### 1. **初始参数**
```c
num = 123
len = 3
sizey = 16 → m = 0 (因sizey≠8)
enshow = 0 (前导零标志)
```
#### 2. **循环处理(逐位分离)**
**第一轮循环 (t=0)**
```c
temp = (123 / oled_pow(10, 3-0-1)) % 10
= (123 / oled_pow(10, 2)) % 10
= (123 / 100) % 10
= (1) % 10
= 1
```
- 前导零检测:`enshow=0` 且 `t(0)<len-1(2)` → 进入判断
- `temp=1≠0` → 设置 `enshow=1`(后续位不再检查前导零)
- 显示:`OLED_ShowChar(0 + (16/2+0)*0, 0, '1', 16)` → 坐标(0,0)显示'1'
**第二轮循环 (t=1)**
```c
temp = (123 / oled_pow(10, 3-1-1)) % 10
= (123 / oled_pow(10, 1)) % 10
= (123 / 10) % 10
= (12) % 10
= 2
```
- `enshow=1` → 跳过前导零检测
- 显示:`OLED_ShowChar(0 + (8)*1, 0, '2', 16)` → 坐标(8,0)显示'2'(字体宽度为8像素)
**第三轮循环 (t=2)**
```c
temp = (123 / oled_pow(10, 3-2-1)) % 10
= (123 / oled_pow(10, 0)) % 10
= (123 / 1) % 10
= 123 % 10
= 3
```
- 显示:`OLED_ShowChar(0 + (8)*2, 0, '3', 16)` → 坐标(16,0)显示'3'
#### 3. **坐标计算原理**
- 字符间距公式:`x + (sizey/2 + m)*t`
- `sizey/2 = 8`(16×16字体的宽度)
- `m = 0`
- 每字符偏移:`t=0 → 0`, `t=1 → 8`, `t=2 → 16`
- 最终显示效果:
```
坐标: (0,0) (8,0) (16,0)
显示: 1 2 3
```
### 关键函数细节说明
#### ▶ `oled_pow(10, n)` 计算过程
```c
// 计算 10^n(n=指数)
u32 oled_pow(u8 m, u8 n) {
u32 result = 1;
while(n--) result *= m; // 循环乘法
return result;
}
```
- **示例**:`oled_pow(10, 2)`
- 初始化:`result=1`
- 第1次循环:`n=2 → result=1*10=10`
- 第2次循环:`n=1 → result=10*10=100`
- 返回 `100`
#### ▶ `OLED_ShowChar` 字符显示流程(以'1'为例)
1. **字模索引计算**:
```c
c = '1' - ' ' = 49 - 32 = 17 // 字库偏移量
```
2. **字模数据获取**:
- 使用16×16字体(`sizey=16`)→ 查表 `asc2_1608[17]`
- 字模数据示例:`{0x00, 0x00, 0x3F, 0x20, 0x20, ...}`(16字节)[^3]
3. **IIC写入过程**:
- 发送从机地址:`0x78`(OLED地址)
- 发送控制字节:`0x00`(指令模式)→ 设置光标位置
- 发送数据字节:`0x40`(数据模式)→ 逐个写入字模字节
```c
// 简化的IIC序列(引用[3])
IIC_Start();
IIC_SendByte(0x78); // 从机地址
IIC_SendByte(0x00); // 指令模式
IIC_SendByte(0xB0 | y); // 设置页地址(y坐标)
IIC_SendByte(0x40); // 数据模式
for(i=0; i<16; i++) IIC_SendByte(asc2_1608[17][i]); // 字模数据
IIC_Stop();
```
### 设计要点总结
1. **动态位分离**:
通过 `(num / 10^{len-t-1}) % 10` 数学运算实现高位到低位的精确提取[^1]。
2. **前导零处理**:
`enshow` 标志确保高位零显示为空格(如 `012` 显示为" 12"),但最低位零始终显示。
3. **坐标自适应**:
公式 `(sizey/2 + m)*t` 自动计算字符间距,支持不同字体尺寸的无缝切换。
***