哈喽大家好,这里是Hello_Embed的第一篇个人笔记,记录了我跟韦东山老师学习嵌入式基础C语言的学习心得,希望对你有所帮助。

单片机是如何存储二进制数据的?

对于单片机来说,它是如何通过电路来存储和表示二进制数据的呢?没错,答案就是“电路”本身
我们通常用一个高电平(比如 3.3V)来表示二进制中的 1,用一个低电平表示 0。一个二进制位(bit)就是由一个这样的电平状态表示的。当我们用 8 个这种高低电平的电路(通常由晶体管组成)组合在一起时,就构成了一个 字节(byte)。为了便于后续学习的理解,我们称这样八个晶体管为八位数据。

💡 补充:
1 byte = 8 bit (1 字节 = 8 位)

int a;  //声明变量a,并分配4个字节

像上面这样的定义,编译器会在内存中为 a 分配 4 字节(即 32 位)的空间。这部分内存用于保存变量的值。
为了验证这一点,我们可以用 Keil5 来进行一个简单的测试

准备阶段:搭建测试环境

在keil中建立工程模板,我这里用的是江协科技的工程模板,相关资料可在b站上查找。

//text.c  这是要用的测试代码
#include <stdio.h>
int mymain(void)
{
	volatile char c;
	volatile int a;
	c = 1;
	a = 2;
	return 0;
}

📌 说明:
volatile 关键字的核心作用是告诉编译器:“这个变量是易变的”,不要对它进行优化处理。这样能确保每次访问都从内存中重新读取变量值。
模板代码示意图如下:

编译并下载程序后,确保烧录器与开发板连接正常,即可开始调试。

调试阶段:观察变量的变化
  1. 在第一个mymain 函数的第一个{处打上断点
  2. 选中变量c与a,右键添加到watch1观察窗口中
  3. 运行Run(F5)
  4. 运行到mymain处再用单步运行Step(F11)一行行调试
    结果如下:

    可以看到,变量 c 的值为 0x01,而变量 a 的值是 0x00000002。这是一个很有意思的现象。
    这是为什么呢?我们来分析一下:
  • cchar 类型,占 1 字节 = 8 位
  • aint 类型,占 4 字节 = 32 位
    上述是二进制显示,由于一个十六进制数位可表示 4 位二进制(2^4 = 16),所以我们可以得到:
  • 8 位二进制(char) -> 2 位十六进制 => 0x01
  • 32 位二进制(int) -> 8 位十六进制 => 0x00000002
拓展:如何查看变量的内存地址?

除了使用 Watch 窗口查看变量值外,我们还可以在 Memory 窗口中查看变量的内存地址。
操作方式:在变量名前加上 & 取地址符号,比如 &a,就可以查看该变量的实际地址了。

关于原码、反码与补码

对于八位二进制数,0000 0000我们将bit7即最高位称为符号位,0为正1为负由此来区分正负数。但这样会出现如下错误:-1 + (+1)= 1000 0001 + 0000 0001 = 1000 0010 = -2。于是我们引出反码:将负数除了符号位的其余部分取反。我们再来计算这个式子-1 + (+1)= 1111 1110 + 0000 0001 = 1111 1111由于这个结果 1111 1111 在反码表示中并不等于 0,说明反码虽然改进了负数表示方式,但在加法运算中仍然存在歧义和进位问题。因此,计算机在实际运算中并不使用反码,而是使用更完善的 补码

  • 正数:补码 = 原码
  • 负数:补码 = 反码 + 1
    我们再用补码计算一次:-1 + (+1)= 1111 1111 + 0000 0001 = 0000 0000(进位丢弃),于是我们终于得出了正确的结果“0”,说明补码确实是一种更适合计算机进行运算的编码方式。
    有了这些编码知识我们来分析一个有意思的现象:
//修改text.c 测试当变量存放值超出数据类型时会发生什么现象
#include <stdio.h>
int mymain(void)
{
	volatile char c;
	volatile int a;
	c = 1234567;
	a = 0x12345678;
	
	printf("c = %d\n\r", c);
	printf("a = %d\n\r", a);
	
	printf("sizeof(c) = %d\n\r", sizeof(c));
	printf("sizeof(a) = %d\n\r", sizeof(a));
	
	return 0;
}

编译后有1个警告:User\text.c(7): warning: #69-D: integer conversion resulted in truncation我们暂时不管它,将程序下载进系统板内,由于这部分用到串口通信我们直接看现象:
c = 135 a = 305419896 sizeof(c) = 1 sizeof(a) = 4
在计算机内进行进制转换,如图:

可以看到二进制为很长的一段数字,但由于char类型变量占1字节也就是8位,取图上低位二进制数有1000 0111这就是keil往单片机存储c的二进制形式,我们将此二进制转为十进制恰好是“135”,说明keil把第一位“1”视作了数值位而非符号位。
我们对变量类型稍加修改就能改善这一情况:volatile char c->volatile unsigned char c,这样c就是一个有符号的整型变量,我们再次打印可得c = -121因为符号位为1,所以我们需要进行补码计算出它的原码(为什么上面不需要?因为上面的符号位被视为数值位,默认补零后就是正数直接计算即可)此时补码经过-1再取反可得:1111 1001取数值位111 1001转换为十进制则是121,加上符号位“1”就是“-121”。

其他数据类型
  • float:可赋小数
  • double:更高精度小数
  • 其他类型如 long longshort
    由于这些类型在嵌入式开发中使用频率相对较低,本文暂不展开,感兴趣的同学可以进一步查阅资料。

写在最后

这是我跟着韦东山老师学习过程中的第一篇动手实验笔记。从变量声明、内存分配,到使用 Keil 调试查看变量值,希望这篇笔记对刚入门的朋友们有所帮助!
如果你也在学习嵌入式,欢迎一起交流成长!
我是 Hello_Embed,一个正在努力成长的嵌入式小白,我们下篇笔记再见!

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐