CPU为什么会有段?why segment/segmentation ?

本文探讨了CPU中段(segment)存在的历史原因,源于早期16位微计算机的内存限制。IBM PC在1981年引入了段地址系统,以解决1MB内存的寻址问题,而不需要超过16位的地址。通过使用复合地址,每个包含两个16位数,实现了超过64K的内存访问。这种设计允许通过不同的组合方式来指代同一内存位置,如763可以通过多种段偏移组合表示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CPU为什么会有段?像cs, ds, ss等等。这个问题困扰了我很久,感觉有了段地址后把问题复杂化了,没必要这么做。

后来翻查资料后才发现,段(segment)的存在原来是有历史因素的:http://homepage.smc.edu/morgan_david/cs40/segmentation.htm

以下是摘抄和个人见解

Vintage 1980 microcomputers used physical addressing, and confined themselves to using 4-digit hexadecimal numbers (which is the same thing as 16 bits) as addresses. The highest you can count with a 4-digit hexadecimal number is FFFF in hex, equivalent to 65535 in decimal. So no more than 65536 bytes or 64K of memory could be used. Even if you could have installed more, the computer could not have used it for lack of ability to refer to it.

1980年的程序是只有16bit大小的内存空间的,也就是只支持64k的内存,寻址只能从0000-FFFF,也就是 汇编 里的 

mov edx AFFFF

这样的指令和程序是不会被执行通过的。


The IBM PC appeared in 1981 and was a fundamental redesign of the earlier microcomputer generation. The designers wanted to allow for 1MB of memory, or 16 times as much as the previous 64K limit. However for design reasons they did not wish to use numbers wider than 16 bits in their addressing system. So they overcame the limit by inventing a system of compound addresses. Each compound address contained 2 16-bit numbers, to be interpreted in a special way. These were the first "segmented addresses" in microcomputers. Coinciding with this was the appearance of a new CPU chip design with new registers to facilitate the new addressing method. (The CPU designers at Intel and the PC architects at IBM worked hand-in-glove designing each piece with the other in mind.)

谁知道,一年后的 1981的IBM PC 改版了CPU的设计,是为了让内存可以达到1MB以上,但最关键的一点是又要保持与1980年的程序兼容,所以聪明的设计者们就想出了‘复合地址’这样的概念。用两个 16位的地址复合在一起,来决定实际的物理地址。

在这里有几问题,

问题1. segment地址是谁输入给CPU的?程序自己吗?还是定义宏? 
(待回答)

问题2:

when he wishes to talk about 763. He now has a way to talk about it, but has successfully avoided using 3-digit numbers. Note he could land on 763 several other ways. For example, by starting at 750 instead of 760, then advancing 13 instead of 3. Just as the 43 yard line on the gridiron is equivalently a 3 yard gain from the 40, a 13 yard gain from the 30, or a 23 yard gain from the 10. All, same thing.  So our engineer could write any of the following to refer to 763:

 76:03
 75:13
 74:23
 73:33
 72:43
 71:53
 70:63
 69:73
 68:83
 67:93


问题2:那么,同一个物理地址763,竟然有那么多的表达方式,所以有很多地址组合都指向同一个物理地址,那会产生什么后果吗?


问题3:

That's it. He can't let his first number go any lower than 67, because that would leave him short of 763 by more than 99, and the second number can only raise him 99 beyond his first one. You can make up the following rule for converting one of these compound addresses into a non-compound (i.e., regular 3-digit) one: to find the 3-digit linear address, take the left number of the compound address, shift it left one place (i.e., multiply it by 10), then add the right number.

这里意思是说,第一个两位数(像地址67:93里的93)最多只能给前一个两位数(67)贡献一个99的位移,再大过百了,就不行了,于是为了达到 763的地址,这里的第一个两位数不能低于67,如果用的是 66:99这样的组合,最大也只是660+99=759这样的组合,产生不了763地址。

结论是:段地址是有限制!那在实际的CPU中,这个限制是如何实现的呢?是通过硬件呢?还是通过软件呢?

问题4,在linux里,已经有了paging 这种技术,那么就不用segmentation这样的地址寻址了,是吗?那就给32-bit 寄存器 register们,赋一个 0000 0000 (假设在32位机器里面),剩下的寻址那就让Offset 地址来做不就行了吗?Offset 地址也是32位的,就是从 0000 0000  ~ FFFF FFFF ,那就是linear address 直接等于 Virtual address, 实际的linux是这么实现的吗?

答:被我猜对了!!! 好激动,在Understanding Linux Kernel 第 42页,我们可以看到linux 就是给这些segment descriptor 的 base 值全部赋起了0x0000 0000!!!!


#include "stm32f10x.h" // ???????? #define MAX_SLOTS 99 // ??????(???),??0 - 9????? const uint8_t SEG_CODE[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; // ?????? // ??????(??)?????? #define DIGIT1_PORT GPIOC #define DIGIT1_PIN GPIO_Pin_11 // ??????(??)?????? #define DIGIT2_PORT GPIOC #define DIGIT2_PIN GPIO_Pin_12 // ???????????? #define SEGMENT_PORT GPIOC // ???????????(A - G?DP) #define SEGMENT_PINS (GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | \ GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8) // ?????????????? #define BUTTON_DOWN_PORT GPIOA #define BUTTON_DOWN_PIN GPIO_Pin_1 // ?????????????? #define BUTTON_UP_PORT GPIOA #define BUTTON_UP_PIN GPIO_Pin_2 // ???????????? #define BUTTON_RESET_PORT GPIOA #define BUTTON_RESET_PIN GPIO_Pin_3 // ??????????? #define BUZZER_PORT GPIOA #define BUZZER_PIN GPIO_Pin_13 // ?????? // ????????,?????????? uint8_t slotCount = 99; // ??????,0?????,1?????? uint8_t alarmState = 0; // ???????,??????????? void Delay(__IO uint32_t nCount) { for(; nCount != 0; nCount--); } // GPIO?????,?????????????? void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStruct; // ??GPIOA?GPIOC???,?????????????? RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE); // ????????????????,???50MHz GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Pin = SEGMENT_PINS; GPIO_Init(SEGMENT_PORT, &GPIO_InitStruct); // ????????????????,???50MHz GPIO_InitStruct.GPIO_Pin = DIGIT1_PIN | DIGIT2_PIN; GPIO_Init(DIGIT1_PORT, &GPIO_InitStruct); // ?????????????,??????,??????? GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStruct.GPIO_Pin = BUTTON_DOWN_PIN | BUTTON_UP_PIN | BUTTON_RESET_PIN; GPIO_Init(BUTTON_DOWN_PORT, &GPIO_InitStruct); // ??????????????,???50MHz GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Pin = BUZZER_PIN; GPIO_Init(BUZZER_PORT, &GPIO_InitStruct); } // ???????,?????????? void Display(uint8_t digit1, uint8_t digit2) { // ?????(??) GPIO_ResetBits(DIGIT1_PORT, DIGIT1_PIN); // ????????(?????) GPIO_SetBits(DIGIT2_PORT, DIGIT2_PIN); // ???????? // ???????,???? GPIO_Write(SEGMENT_PORT, 0x00); Delay(100); // ????,?????? // ??????,????????? GPIO_Write(SEGMENT_PORT, SEG_CODE[digit1]); Delay(500); // ????(?2.5ms @ 72MHz) // ?????(??) GPIO_SetBits(DIGIT1_PORT, DIGIT1_PIN); // ???????? GPIO_ResetBits(DIGIT2_PORT, DIGIT2_PIN); // ???????? // ???????? GPIO_Write(SEGMENT_PORT, 0x00); Delay(100); // ???? // ??????,????????? GPIO_Write(SEGMENT_PORT, SEG_CODE[digit2]); Delay(500); // ???? } // ??????????,???????????? uint8_t ButtonPressed(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { if (GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == Bit_RESET) { Delay(20000); // ????(?10ms) if (GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == Bit_RESET) { while (GPIO_ReadInputDataBit(GPIOx, GPIO_Pin) == Bit_RESET); // ?????? return 1; // ????????1 } } return 0; // ????????0 } // ??????,????????????????? void CheckButtons(void) { // ????????(??PA1??) if (ButtonPressed(BUTTON_DOWN_PORT, BUTTON_DOWN_PIN)) { if (slotCount > 0) { slotCount--; alarmState = 0; // ???????,?????? } else { alarmState = 1; // ???0?,?????? } } // ????????(??PA2??) if (ButtonPressed(BUTTON_UP_PORT, BUTTON_UP_PIN)) { if (slotCount < MAX_SLOTS) { slotCount++; alarmState = 0; // ???????,?????? } } // ??????(??PA3??) if (ButtonPressed(BUTTON_RESET_PORT, BUTTON_RESET_PIN)) { slotCount = 99; alarmState = 0; // ?????????,?????? } } // ???????,??????????? void Beep(void) { // ???????????,???? GPIO_SetBits(BUZZER_PORT, BUZZER_PIN); Delay(100000); // ???50ms // ???????????,???? GPIO_ResetBits(BUZZER_PORT, BUZZER_PIN); } int main(void) { GPIO_Configuration(); // ???GPIO while (1) { // ?????????????????? uint8_t digit1 = slotCount / 10; uint8_t digit2 = slotCount % 10; // ???????????,??????? Display(digit1, digit2); Display(digit1, digit2); // ????????? CheckButtons(); // ??????????? if (alarmState) { Beep(); Delay(100000); // ?????? } } }为什么数码管不显示数字显示乱码
最新发布
05-29
<think>嗯,用户之前问的是关于STM32F10X数码管显示乱码的问题,需要找出原因和解决方案。首先,我得回忆一下数码管的工作原理和常见的乱码情况。数码管通常有共阳或共阴类型,驱动方式可能涉及到动态扫描,所以硬件连接是否正确是关键。可能的问题包括引脚接触不良、限流电阻不合适或者驱动电流不足。接下来,软件方面,动态扫描的频率如果太低,会导致闪烁,而太高可能会让数码管无法正确点亮。比如,如果延时设置不当,或者中断干扰了扫描时序,就会导致显示异常。此外,编码表是否正确也很重要,比如码和位码是否对应正确,特别是小数点位置有没有处理错误。然后,STM32的GPIO配置是否正确?比如推挽输出和开漏输出的区别。数码管需要足够的电流驱动,如果GPIO设置为开漏而没有上拉电阻,可能导致电压不足,显示暗淡或乱码。另外,复用功能或者配置成输入模式也可能导致问题。 还有可能涉及到中断或DMA冲突。比如,如果显示程序被高优先级中断打断,动态扫描的时序就会被打乱,导致显示错乱。或者DMA传输数据错误,缓冲区没有正确更新,显示的数据就会混乱。 电源方面,电压不稳定或者电流不足,也会导致数码管显示异常。比如,当多个同时点亮时,电流需求增大,如果电源无法提供足够电流,可能会造成电压下降,影响显示效果。 解决方案的话,硬件方面需要检查接线、电阻、驱动电路和电源。软件方面要确认扫描频率、编码表正确性、GPIO配置和中断处理。另外,可能还需要用示波器或逻辑分析仪来检查信号时序,或者逐步调试代码,确认每个部分是否正常工作。用户提供的引用里提到了STM32F10X的异常响应系统和SPI/I2C的区别,虽然这些不是直接相关,但可能涉及到中断配置或外设冲突。比如,错误的中断优先级设置可能导致显示中断被其他中断打断,从而影响扫描时序。需要检查中断配置,确保显示相关的中断有足够的优先级,或者优化中断服务例程的执行时间。最后,生成的相关问题需要围绕可能的原因展开,帮助用户进一步排查问题,比如硬件连接、驱动电流、软件配置、信号干扰等。</think>### STM32F10x 数码管显示乱码原因及解决方案 #### 一、硬件原因 1. **引脚接触不良或虚焊** - 检查数码管与STM32的GPIO连接是否牢固,使用万用表测量通断性。 - 验证限流电阻阻值是否合理,例如:$R = \frac{V_{CC}-V_{LED}}{I_{LED}}$,典型值在220Ω-1kΩ之间[^1]。 2. **驱动电流不足** - 共阳数码管需确认外部电源供电能力,共阴数码管需检查STM32 GPIO的灌电流能力(最大25mA)。 - 若驱动多位数码管,建议增加三极管或专用驱动芯片(如74HC595)。 3. **信号干扰** - 长导线可能引入电磁干扰,建议缩短走线或增加滤波电容(如0.1μF贴片电容并联在电源引脚)。 #### 二、软件原因 1. **动态扫描时序错误** - 动态扫描频率建议在50Hz-200Hz之间,计算公式: $$T_{scan} = \frac{1}{N \times f_{refresh}}$$ 其中$N$为数码管位数,$f_{refresh}$为视觉暂留刷新率(通常≥60Hz)。 2. **编码表错误** - 对比硬件接线修正码表,例如共阴数码管字符`0`的编码可能是`0x3F`,需根据实际接线调整: ```c const uint8_t seg_code[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; // 0-9编码 ``` 3. **GPIO配置问题** - 确认GPIO模式设置为推挽输出(GPIO_Mode_Out_PP): ```c GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; ``` #### 三、系统级问题 1. **中断干扰** - 动态扫描若在定时器中断中实现,需确保中断优先级高于其他耗时中断,避免扫描被打断[^1]。 2. **电源噪声** - 使用示波器测量VCC波形,若纹波过大(如>100mV),需增加稳压电路或去耦电容。 #### 四、调试方法 1. **分步验证法** - 先点亮单(如固定显示字符`8`),再逐步启用动态扫描。 - 使用逻辑分析仪捕获GPIO时序,确认扫描间隔和编码输出是否符合预期。 2. **代码示例(基础扫描逻辑)** ```c void Display_Scan(void) { static uint8_t pos = 0; GPIO_Write(DIG_PORT, ~(0x01 << pos)); // 位选(共阴) GPIO_Write(SEG_PORT, seg_code[display_buf[pos]]); // 码 pos = (pos + 1) % 4; // 4位数码管循环 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值