嵌入式开发常用技巧及编程知识

article/2025/10/30 16:42:16

嵌入式开发常用技巧及C/C++知识

  • 引言
    • 查询程序占据的内存大
    • static 静态变量
      • 介绍
      • static在函数中的用法
    • ‘##’连接符
    • 断言函数
    • 宏定义与条件变量
      • #if...#else...#endif
      • 选择是否使用串口调试
    • memcpy函数
    • void 指针
      • 指针大小
    • 字符串小写转大写
    • 字符串大写转小写
    • 字符串命令处理
    • 将某几位清0,并保留其他位的状态
    • strstr()
    • atoi()
    • rename()
    • 其他
  • 后续

引言

我们在日常的嵌入式开发中,经常会遇到各种C/C++的使用问题,并且C/C++纯软件的常用开发技巧有些嵌入式并不常用,而嵌入式开发中使用到的C/C++知识与技巧有些也非常特别,这里我们来具体介绍一下嵌入式开发常用技巧及C/C++知识(未完待续)。

查询程序占据的内存大

双击你的工程文件名

打开.map文件,拉到最下面就可以看到你的程序会占据开发板的多少内存。

static 静态变量

介绍

静态全局变量:在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。
静态全局变量有以下特点:

  1. 该变量在全局数据区分配内存
  2. 未经初始化的静态全局变量会被程序自动初始化为0(在函数体内声明的自动变量的值是随机的,除非它被显式初始化,而在函数体外被声明的自动变量也会被初始化为0)
  3. 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的
    静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况:
代码区low address
全局数据区堆区栈区high address

一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:

  1. 静态全局变量不能被其它文件所用
  2. 其它文件中可以定义相同名字的变量,不会发生冲突

static在函数中的用法

当函数中定义一个static变量,除了第一次调用这个函数会定义这个变量以外,其他情况下,均不会重新定义了。下面举个例子,对比静态变量和常规变量在函数调用中的区别。

void staticFun(void)
{static  uint8_t  data = 0;data++;printf("static function data = %d\r\n",data);
}
void NostaticFun(void)
{uint8_t  data = 0;data++;printf("no static function data = %d\r\n",data);
}
int main()
{staticFun();
staticFun();
staticFun();NostaticFun();
NostaticFun();
NostaticFun();return 0;}

执行此程序,主函数会先调用三次staticFun();函数,再调用三次NostaticFun();函数。最后的输出结果为:

1
2
3
1
1
1

因为每次NostaticFun中的data 都会被重新定义,而staticFun中的data不会重复定义。

‘##’连接符

##用来连接前后两个参数,把它们变成一个字符串。
例子如下:

#define main(x,y) x##y
int xy=1;
cout < < main(x,y) < < endl; 

将会使编译器把
cout < < main(x,y) < < endl;
解释为
cout < < xy < < endl;
理所当然,将会在标准输出处显示’1’。
从此可以看出,x##y的效果就是将x和y连在一起了。
而#define main(x,y) x##y 则相当于把main(x,y)等价于x##y

断言函数

主要作用:是对一个bool型表达式进行检查,一个正确运行的程序必须保证这个bool型表达式的值为true,若表达式的值为false,则说明程序已处于一种不正确的状态下,系统需要提供警告信息并且退出程序。
在实际开发中assert主要用于保证程序的正确性,通常在程序开发和测试时使用。为了提高运行效率,在软件发布后,assert检查默认是关闭的。
使用断言的几个原则:

  1. 使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
  2. 使用断言对函数的参数进行确认。
  3. 一般教科书都鼓励程序员们进行防错性的程序设计,但要记住这种编程风格会隐瞒错误。当进行防错性编程时,如果"不可能发生"的事情的确发生了,则要使用断言进行报警。
    FreeRTOS 中的 configASSERT
    configASSERT是在FreeRTOS中的断言函数。如果断言函数的参数为0时将触发断言函数的执行。
    FreeRTOS的断言功能在调试阶段是非常有用的,可以有效地检查参数错误和运行中的错误,但在正式发布软件时,请将此功能关闭,因为断言功能会增加工程代码大小并降低工程执行效率。关闭断言也比较简单,如果FreeRTOSConfig.h文件中有断言的宏定义,将其注释掉即可,如果没有宏定义,默认在FreeRTOS.h文件中就是关闭的。
#define configASSERT( x )     if( x == 0 ) {taskDISABLE_INTERRUPTS(); for(;;); }

在使用调试器的情况下,一旦出现断言失败,会关闭中断,程序会死在这个for循环中,此时用户可以很容易就锁定函数出错位置。

宏定义与条件变量

#if…#else…#endif

我们在调试程序时,经常会遇到某段功能的实现,写了两种版本的程序,但调试时又不想来回切换。,这时候我们可以使用条件变量。
比如:想测试__set_FAULTMASK(1);和__disable_fault_irq();的区别,就可以使用如下方式,只需要更改#if后面是1还是0就可以选择是使用哪段程序。

	 #if 1
//	  __set_FAULTMASK(1);NVIC_SystemReset();	 #else__disable_irq();delay_ms(1000);__disable_fault_irq();NVIC_SystemReset();#endif

选择是否使用串口调试

我们在程序开发过程时,往往使用串口进行程序的调试,但在产品成熟时,为了避免内存开销和其他的一些问题往往会去掉串口调试,这时候应该如何简单的去掉呢,下面我来介绍一下。

#define DEBUG_EN 1 //选择是否打开printf调试(串口一),1代表打开,0代表关闭
#if DEBUG_EN#define    DEBUG(fmt, ...)     do{ printf(fmt , ##__VA_ARGS__); }while(0)
#else#define    DEBUG    do{  }while(0);
#endif

我们通过上述程序,就可以实现串口调试的全部切换,非常方便。

memcpy函数

memcpy函数的用法,memcpy (void* _Dst,void const* _Src,size_t _Size)
memcpy函数是将后面地址的内容一个数据一个数据放在前面的地址,注意,是先放低位。
_Size是字节数,也就是说如果是32位数组,两个数组值就应该是_Size就应该是4。
例子:

char a[8]={0x12,0x34,0x56,0x78,0x90,0x14,0x52,0x46 };
short b=0;
memcpy(&b,a+1,2);
printf("b=%x", b);
此段代码的作用是把0x340x56拼接起来送到b,输出的最终结果是:0x5634

void 指针

void 指针可以指向任意类型的数据,就是说可以用任意类型的指针对 void 指针对 void 指针赋值。如果要将 void 指针 p 赋给其他类型的指针,则需要强制类型转换,就本例而言:a=(int *)p。在内存的分配中我们可以见到 void 指针使用:内存分配函数 malloc 函数返回的指针就是 void * 型,用户在使用这个指针的时候,要进行强制类型转换,也就是显式说明该指针指向的内存中是存放的什么类型的数据 (int )malloc(1024) 表示强制规定 malloc 返回的 void 指针指向的内存中存放的是一个个的 int 型数据。

int *Q;
void *P;
P=Q;

我们可以看到void指针类型是可以指向int *指针类型的。

指针大小

在64位系统中,不管什么样的基类型,系统指针给指针变量分配的内存空间都是8字节,在C语言中,指针变量的“基类型”仅用来指定该指针变量可以指向的变量类型,并没有其他意思。 不管基类型是什么类型的指针变量,他仍然是指针变量,所以仍然占用 8 字节。

字符串小写转大写

//将字符串中的小写字母转换为大写
//str:要转换的字符串
//len:字符串长度
void litterTobig(u8 *str,u8 len)
{u8 i;for(i=0;i<len;i++){if((96<str[i])&&(str[i]<123))	//小写字母str[i]=str[i]-32;				//转换为大写}
}

字符串大写转小写

int8_t* CapToLow(int8_t* str)
{int i;for (i = 0; i < sizeof(str); i++){if ((64 < str[i]) && (str[i] < 91)) //大写str[i] = str[i] + 32;           //小写}return str;
}

字符串命令处理

通常我们在串口收发或者上下位机的控制中,会使用字符串进行命令的下发,但我们的单片机肯定使用数值会更容易控制些,所以我们可以使用一个控制函数和一些宏定义来完成。

  1. 宏定义命令
//用于命令解析用的命令值
#define LED1ON	1
#define LED1OFF	2
#define BEEPON	3
#define BEEPOFF	4
#define COMMANDERR	0XFF
  1. 命令处理函数,将字符串命令转换成命令值
//str:命令
//返回值: 0XFF,命令错误;其他值,命令值
u8 CommandProcess(u8 *str)
{u8 CommandValue=COMMANDERR;if(strcmp((char*)str,"LED1ON")==0) CommandValue=LED1ON;else if(strcmp((char*)str,"LED1OFF")==0) CommandValue=LED1OFF;else if(strcmp((char*)str,"BEEPON")==0) CommandValue=BEEPON;else if(strcmp((char*)str,"BEEPOFF")==0) CommandValue=BEEPOFF;return CommandValue;
}
  1. 使用方式
Value=CommandProcess(CommandStr);		//命令解析

此时Value就是我们转换成的命令数值(1、2、3、4这类)

将某几位清0,并保留其他位的状态

使用”&= ~"进行清零。
我们以下面的程序为例:

uint32_t ultmp;
ultmp=0x12345678;
ultmp&= ~(0XFFFF0000);
printf("ultmp=0x%d\n",ultmp);

上述程序的作用就是将ultmp的高16位 置0,低16位保留.
最后输出的结果是0x00005678。

strstr()

此函数在嵌入式的日常开发中使用频繁,功能为:在字符串 A中查找第一次出现字符串B的位置。
C 标准库 - <string.h>
C 库函数 char *strstr(const char *haystack, const char *needle) 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 ‘\0’。

atoi()

C 标准库 - <stdlib.h>

描述
C 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)。

rename()

描述
C 库函数 int rename(const char *old_filename, const char *new_filename) 把 old_filename 所指向的文件名改为 new_filename。
参数
old_filename – 这是 C 字符串,包含了要被重命名/移动的文件名称。
new_filename – 这是 C 字符串,包含了文件的新名称。
返回值
如果成功,则返回零。如果错误,则返回 -1,并设置 errno。

其他

  1. %*c表示忽略一个字符

  2. C++变量前面加下划线和不加下划线都不会影响对变量的定义,只是风格问题,更喜欢将成员变量或者私有成员变量的前面加上下划线。以表示该变量是某个类的属性。

后续

如果想了解更多物联网、智能家居项目知识,可以关注我的公众号了解更多。


http://chatgpt.dhexx.cn/article/vmGPA5xK.shtml

相关文章

嵌入式程序编写方法与规范

嵌入式程序编写方法与规范 前言 本文主要讲解嵌入式单片机程序的编写方法以及编写规范&#xff0c;以MSP430单片机作为例子&#xff0c;无论是51,AVR还是STM32单片机都同样适用&#xff0c;本文对C语言各种语法各种关键字进行详细解释&#xff0c;对操作物理地址的方法进行剖析…

嵌入式系统C语言编程基础

目录 关于本环节前言专栏为什么进行本环节 小测验解答 C语言复习1.循环与分支2.作用域与存储类3.内存与指针指针 4.位操作(1)位操作的用途(2)位运算符(3)用法&#xff1a;掩码(4)用法&#xff1a;打开位、关闭位、转置位(5)用法&#xff1a;查看某一位的值(6)用法&#xff1a;移…

密码学学习笔记三:同余定理

同余定理 我们在《密码学学习笔记二&#xff1a;RSA加密法》里面提到过同余&#xff0c;此处把同余作为补充知识&#xff0c;单独写一篇文章讲解一下。 同余定理是数论中的重要概念。给定一个正整数m&#xff0c;如果两个整数a和b满足&#xff08;a-b&#xff09;能够被m整除&a…

简单理解-同余定理

本文章仅用于笔记。部分知识点来源于网络&#xff0c;授权请联系作者&#xff08;947043511qq.com&#xff09;。 直接抛出自己的理解&#xff1a; 2个不同的整数a、b&#xff0c;被一个整数m相除时&#xff0c;得到相同的余数&#xff0c;那么我就可以称a、b同余。 因为a、b同…

以太坊EVM源码注释之执行流程

以太坊EVM源码分析之执行流程 业务流程概述 EVM是用来执行智能合约的。输入一笔交易&#xff0c;内部会将之转换成一个Message对象&#xff0c;传入 EVM 执行。在合约中&#xff0c;msg 全局变量记录了附带当前合约的交易的信息&#xff0c;可能是为了一致&#xff0c;这里也…

【密码学探秘】EVM链和并行执行交易

概述 在web3.0世界中&#xff0c;交易的处理性能一直是公链面临的一大技术挑战&#xff0c;如何在不降低安全性和去中心化程度的前提下显著的提升区块链交易的TPS无疑成为众多公链技术专家追逐的目标。以Solana、Aptos为代表的新一代公链的出现更是吹响了通过并行执行交易来攻…

MATLAB计算EVM函数

function [rmsEVM,maxEVM,pctEVM,numSym] EVMcalculate(RxSig,TxSig)%RxSig为接收信号解调后复基带信号&#xff1b;TxSig是发射信号的复基带信号。 %计算接收信号EVM和画星座图 %RxSig,TxSig长度要一样&#xff1b; evm comm.EVM(‘MaximumEVMOutputPort’,true,… ‘XPerce…

通信算法之149:EVM测量

1.星座图 h scatterplot(sqrt(sps)*txSig(sps*span1:end-sps*span),sps,offset); hold on scatterplot(rxSigFilt(span1:end-span),n,offset,bx,h) scatterplot(dataMod,n,offset,r,h) legend(Transmit Signal,Received Signal,Ideal,location,best) 2. 眼图 Eye Diagram D…

以太坊虚拟机 EVM(2)Solidity运行原理

作者&#xff1a;储雨知&#xff5c;FISCO BCOS 核心开发者 引 言 作为一门面向智能合约的语言&#xff0c;Solidity与其他经典语言既有差异也有相似之处。 一方面&#xff0c;服务于区块链的属性使其与其他语言存在差异。例如&#xff0c;合约的部署与调用均要经过区块链网…

IMA/EVM完整性检测代码分析

IMA/EVM完整性检测 IMA&#xff08;Integrity Measurement Architecture&#xff09;是一个内核安全子系统&#xff0c;用于检测文件或数据的完整性和安全性。IMA的hook机制指的是内核接口钩子&#xff08;kernel interface hooks&#xff09;&#xff0c;用于向IMA注册和实现…

为何Cable Loss没补好 EVM会变差

当EVM不好 或是灵敏度不好时 先别急着找硬件问题 先检查Cable Loss 先说结论 先谈谈标题 为何Cable Loss没补好 EVM会变差 多数射频功放输出 会接一个耦合器 将输出功率 耦合到收发器 用意是校正时 侦测输出功率的正确性与否 假设天线头为20dBm Cable loss为5dB 假设天线头…

TI毫米波级联雷达评估板 MMWCAS-DSP-EVM 和MMWCAS-RF-EVM

1. 前言 本文主要是TI的MMWCAS-DSP-EVM 和MMWCAS-RF-EVM 两块评估板的一些使用心得和毫米波雷达的学习总结。 2. 相关原理 毫米波(mmWave)是一类使用短波长电磁波的特殊雷达技术。通过捕捉反射的信号&#xff0c;雷达系统可以确定物体的距离、速度和角度。毫米波雷达可发射波…

DCA1000EVM使用指南

DCA1000EVM使用指南 一、开发环境 1、硬件 AWR1243/xWR1443/xWR1642BOOST&#xff08;本文以IWR1642BOOST为例&#xff09;DCA1000EVM5V/2.5A&#xff08;电流要求不小于2.5A&#xff09;电源适配器1个或2个 micro USB线2条RJ45网线1根60引脚Samtec连接线&#xff08;DCA100…

EVM误差矢量幅度

EVM误差矢量幅度是衡量调制精度的一个主要指标&#xff0c;镜像频率的抑制度对EVM的影响取决于镜像频率的抑制度&#xff0c;一般镜像频率抑制度达到31dBc时&#xff0c;对EVM的影响约为5%。镜像频率对信号的影响是因为镜像频率的信号带宽与所需信号的带宽一样&#xff0c;而无…

区块链 以太坊 虚拟机 EVM 详解

一、虚拟机 虚拟机用来 执行以太坊上的交易&#xff0c;更改以太坊状态。 交易分两种&#xff1a; 普通交易智能合约交易。 在执行交易时需要支付油费。 智能合约之间的调用有四种方式。 二、以太坊虚拟机 以太坊虚拟机&#xff0c;简称 EVM&#xff0c;是用来执行以太坊…

以太坊EVM智能合约中的数据存储

目录 EVM基本信息 数据管理 Stack Args Memory Storage 固定长度的值 动态长度数组 Mappings 复杂类型的组合 总结 EVM基本信息 以太坊是一种基于栈的虚拟机&#xff0c;基于栈的虚拟机数据的存取为先进先出&#xff0c;在后面介绍EVM指令的时候会看到这个特性。同时基…

以太坊虚拟机EVM究竟是个啥

基本概念 EVM&#xff08;ETHereum Virtual Machine&#xff09;是「以太坊虚拟机」的缩写。如果你有一些软件开发的背景&#xff0c;一定听过java虚拟机。通俗的解释java虚拟机的就是&#xff1a; 我们写的java代码&#xff08;不只是java&#xff0c;groovy,scala等也可以&a…

介绍 EVM 等效性

介绍 EVM 等效性 上个月&#xff0c;我们宣布了Optimistic 以太坊历史上最重要的升级。最近&#xff0c;我们将Optimistic Kovan迁移到真正的一键式部署&#xff0c;并增加了稳定性&#xff0c;主网在不到三周的时间内就会跟上。 这篇文章是关于我们相信EVM 等效性——完全符合…

EVM的深入研究和分析

最终目标是能够完整地理解已编译的Solidity合同 1、执行 evm-tools 安装 https://github.com/CoinCulture/evm-tools/blob/master/INSTALL.md /home/xue/go/bin/evm --debug --code 366020036101000a600035045b6001900380600c57 --input 05 不同字节码编译成不同EVM指令 1、基…

Ethereum EVM简介

1. 引言 首先需了解以下基本概念&#xff1a; 1&#xff09;区块链2&#xff09;世界状态3&#xff09;账号4&#xff09;交易5&#xff09;消息6&#xff09;去中心化账本7&#xff09;原子性和顺序性 1.1 何为以太坊区块链&#xff1f; 以太坊可看成是基于交易的状态机&a…