STM32学习第三课:STM32 c语言学习基础3(内存操作、指针、结构指针)

发布者:西周以前的最新更新时间:2022-07-01 来源: csdn关键字:STM32  c语言学习  内存操作  指针 手机看文章 扫描二维码
随时随地手机看文章

1.内存操作


在对内存操作头疼的时候我发现了这篇神奇的文章,拜读之后豁然开朗心生崇拜


数据指针


嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调试中,多借助C语言指针所具有的对绝对地址单元内容的读写能力。以指针直接操作内存多发生在如下几种情况:


(1) 某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于某特定地址;


(2) 两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单元(称为mail box)书写内容以在对方CPU产生中断;


(3) 读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。


譬如:

在这里插入图片描述

以上程序的意义为在绝对地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)写入11。


在使用绝对地址指针时,要注意指针自增自减操作的结果取决于指针指向的数据类别。上例中p++后的结果是p= 0xF000FF01,若p指向int,即:

在这里插入图片描述

p++(或++p)的结果等同于:p = p+sizeof(int),而p-(或-p)的结果是p = p-sizeof(int)。


同理,若执行:

在这里插入图片描述

则p++(或++p)的结果等同于:p = p+sizeof(long int) ,而p-(或-p)的结果是p = p-sizeof(long int)。


记住:CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。


函数指针


首先要理解以下三个问题:


(1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;


(2)调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;


(3)因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以"调用"一个根本就不存在的函数实体,晕?请往下看:


请拿出你可以获得的任何一本大学《微型计算机原理》教材,书中讲到,186 CPU启动后跳转至绝对地址0xFFFF0(对应C语言指针是0xF000FFF0,0xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码:

在这里插入图片描述

在以上的程序中,我们根本没有看到任何一个函数实体,但是我们却执行了这样的函数调用:lpReset(),它实际上起到了"软重启"的作用,跳转到CPU启动后第一条要执行的指令的位置。


记住:函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!


数组vs.动态申请


在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会很快导致系统的崩溃。


所以一定要保证你的malloc和free成对出现,如果你写出这样的一段程序:

在这里插入图片描述

在某处调用function(),用完function中动态申请的内存后将其free,如下:

在这里插入图片描述

上述代码明显是不合理的,因为违反了malloc和free成对出现的原则,即"谁申请,就由谁释放"原则。不满足这个原则,会导致代码的耦合度增大,因为用户在调用function函数时需要知道其内部细节!


正确的做法是在调用处申请内存,并传入function函数,如下:

在这里插入图片描述

而函数function则接收参数p,如下:

在这里插入图片描述

基本上,动态申请内存方式可以用较大的数组替换。对于编程新手,笔者推荐你尽量采用数组!嵌入式系统可以以博大的胸襟接收瑕疵,而无法"海纳"错误。毕竟,以最笨的方式苦练神功的郭靖胜过机智聪明却范政治错误走反革命道路的杨康。


给出原则:


(1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统);


(2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且malloc和free应成对出现!


关键字const


const意味着"只读"。区别如下代码的功能非常重要,也是老生长叹,如果你还不知道它们的区别,而且已经在程序界摸爬滚打多年,那只能说这是一个悲哀:

在这里插入图片描述

(1) 关键字const的作用是为给读你代码的人传达非常有用的信息。例如,在函数的形参前添加const关键字意味着这个参数在函数体内不会被修改,属于"输入参数"。在有多个形参的时候,函数的调用者可以凭借参数前是否有const关键字,清晰的辨别哪些是输入参数,哪些是可能的输出参数。


(2)合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改,这样可以减少bug的出现。


const在C++语言中则包含了更丰富的含义,而在C语言中仅意味着:“只能读的普通变量”,可以称其为"不能改变的变量"(这个说法似乎很拗口,但却最准确的表达了C语言中const的本质),在编译阶段需要的常数仍然只能以#define宏定义!故在C语言中如下程序是非法的:

在这里插入图片描述

(注:C99后这个应该是合法的。)


关键字volatile


C语言编译器会对用户书写的代码进行优化,譬如如下代码:

在这里插入图片描述

很可能被编译器优化为:

在这里插入图片描述

但是这样的优化结果可能导致错误,如果I/O空间0x100端口的内容在执行第一次读操作后被其它程序写入新值,则其实第2次读操作读出的内容与第一次不同,b和c的值应该不同。在变量a的定义前加上volatile关键字可以防止编译器的类似优化,正确的做法是:


volatile int a;


volatile变量可能用于如下几种情况:


(1) 并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);


(2) 一个中断服务子程序中会访问到的非自动变量(也就是全局变量);


(3) 多线程应用中被几个任务共享的变量。


CPU字长与存储器位宽不一致处理


在背景篇中提到,本文特意选择了一个与CPU字长不一致的存储芯片,就是为了进行本节的讨论,解决CPU字长与存储器位宽不一致的情况。80186的字长为16,而NVRAM的位宽为8,在这种情况下,我们需要为NVRAM提供读写字节、字的接口,如下:


typedef unsigned char BYTE;

typedef unsigned int WORD; 

/* 函数功能:读NVRAM中字节 

* 参数:wOffset,读取位置相对NVRAM基地址的偏移

* 返回:读取到的字节值

*/

extern BYTE ReadByteNVRAM(WORD wOffset)

{

 LPBYTE lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* 为什么偏移要×2? */


 return *lpAddr;

}


/* 函数功能:读NVRAM中字

* 参数:wOffset,读取位置相对NVRAM基地址的偏移

* 返回:读取到的字

*/

extern WORD ReadWordNVRAM(WORD wOffset)

{

 WORD wTmp = 0;

 LPBYTE lpAddr;

 /* 读取高位字节 */

 lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* 为什么偏移要×2? */ 

 wTmp += (*lpAddr)*256;

 /* 读取低位字节 */

 lpAddr = (BYTE*)(NVRAM + (wOffset +1) * 2); /* 为什么偏移要×2? */

 wTmp += *lpAddr;

 return wTmp;

}


/* 函数功能:向NVRAM中写一个字节 

*参数:wOffset,写入位置相对NVRAM基地址的偏移

* byData,欲写入的字节

*/

extern void WriteByteNVRAM(WORD wOffset, BYTE byData)

{

 …

}


/* 函数功能:向NVRAM中写一个字 */

*参数:wOffset,写入位置相对NVRAM基地址的偏移

* wData,欲写入的字

*/

extern void WriteWordNVRAM(WORD wOffset, WORD wData)

{

 …

}


子贡问曰:Why偏移要乘以2?


子曰:请看图1,16位80186与8位NVRAM之间互连只能以地址线A1对其A0,CPU本身的A0与NVRAM不连接。因此,NVRAM的地址只能是偶数地址,故每次以0x10为单位前进!

在这里插入图片描述

2.指针


整形指针定义:


int a = 0;


int *pa = &a ;


指针的含义:指针实际上是一种存放地址的数据类型,比如上面的例子变量pa就可以被视为int *类型,pa中存放的是变量a的地址,变量a占据四个字节,a地址可以用首地址来代替,pa是个地址并指向a所占的这四个字节。pa本身占据8个字节,根据存放变量的类型不同,指针类型也应该与变量对应,对于64位系统,但不管什么类型的指针,都占据8个字节,地址均为64位也就等于8个字节。当然对于32位系统来说,不同类型的指针指针均占据4个字节。


:可以代表两种含义

前面有数据类型时,表示定义指针类型,比如 int *, char *,short *,double *等等

后面跟地址或指针时,表示修改这个地址或指针的值,比如 int *pa= &a; *pa = 1;

在这里插入图片描述

指针加1表示指针向后移动一个数据类型长度的地址。


比如整形指针,整形占据4个字节,整形指针内存放的地址时62FE0C,将指针加1后,指针地址向后移动4个字节。同理字符型占1个字节,指针加1后,地址向后移动1个字节.


参考代码如下:


#include


int main()


{


    int a = 0;


    int *pa = &a;


    printf ("整形指针:%p  整形指针+1:%pn", pa, pa+1) ;


    printf ("%  整形指针占字节数:%dn", sizeof(pa)) ;


    char ch = 'a';


    char *pch = &ch;


    printf("字符指针:%p  字符指针+1:%pn", pch, pch+1);


    printf ("%  整形指针占字节数:%dn", sizeof(pch)) ;


    return 0;


}


3.结构体指针


函数指针在C语言中的意义

在C语言程序中,数据结构和算法是两个基本的元素。C语言的基本数据类型、结构体、数组和联合体是数据结构的代表;C语言中的函数则是算法的代表。只有将数据结构和算法有机结合才能构成具有一定功能的程序。


函数指针的应用

函数指针在嵌入式中的应用非常广泛,常常把函数指针作为结构体的成员、作为函数的参数等。如在物联网操作系统RT-Thread内核源码中,有如下代码:


1、函数指针作为结构体成员

在这里插入图片描述

2、函数指针作为函数的参数

举例说明

建立一个结构体,用于四则运算(根据函数指针的指向可以选择加法运算、减法运算、乘法运算、除法运算),如


typedef int (*fun_t)(int, int);    


// 包含了数据和算法的结构体

struct Source

{

 int a;        // 数据a

 int b;        // 数据b

 fun_t operation;  // 算法operation

};


主函数内可以进行如下操作:


struct Source data;

int result;

data.a = 200;

data.b = 100;

/* 两个数相加的操作 */

data.operation = add2;  

result = data.operation(data.a, data.b);

printf("加法运算: %d+%d = %dn",data.a, data.b, result);


函数指针data.operation指向加法函数add2,则调用data.operation就可以进行加法运算。同理,有:


/* 两个数相减的操作 */

data.operation = sub2;

result = data.operation(data.a, data.b);

printf("减法运算: %d-%d = %dn",data.a, data.b, result);


/* 两个数相乘的操作 */

data.operation = mul2;

result = data.operation(data.a, data.b);

printf("乘法运算: %d*%d = %dn",data.a, data.b, result);


/* 两个数相除的操作 */

data.operation = div2;

result = data.operation(data.a, data.b);

printf("除法运算: %d/%d = %dn",data.a, data.b, result);

关键字:STM32  c语言学习  内存操作  指针 引用地址:STM32学习第三课:STM32 c语言学习基础3(内存操作、指针、结构指针)

上一篇:STM32学习第四课:STM32 c语言学习基础4
下一篇:STM32学习第二课:STM32c语言基础2

推荐阅读最新更新时间:2024-11-02 07:29

STM32 HAL库串口收发是如何使用的?
STM32是一款高性能的微控制器,它拥有广泛的应用领域,其中包括了各种通讯应用,如UART串口通讯。HAL库是ST公司为了方便开发者使用STM32而开发的一种库,它提供了一种简单易用的方法来使用STM32的各种外设。 本文将详细介绍如何使用STM32 HAL库来进行串口通信,包括初始化、发送数据和接收数据等方面。 1. 初始化串口 首先需要初始化串口外设,按照HAL库的方法,我们需要定义一个串口句柄,然后对句柄中的各项参数进行赋值,包括波特率、数据位、停止位、奇偶校验位等等。根据不同的外设,具体的初始化内容可能会稍有不同。 示例代码如下: ```c UART_HandleTypeDef huart; void UART_Init(
[单片机]
STM32printf函数实现方法
这几天学习stm32发现利用keil不能正常使用printf函数,所以我去网上找了一下,这是一个网易博主的解决办法,亲测有效: STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK) 在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。 下面就说一下使用printf需要做哪些配置。 有两种配置方法: 一、对工程属性进行配置,详细步骤如下 1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。 2、在main文件中重定义 /
[单片机]
STM32 ST-LINK Utility无法下载的处理方法
现象:提示family: Unknown device Read out protection is activated. Could not disable Read Out Protection! 分析:由于STM内部提供了数据保护,有读出保护和写保护。禁止读出保护起作用,那么下载器就读不出内容了。是为了防止盗版的功能。做产品应该使用。 解决办法:只能把写保护、读保护取消,并擦出整个芯片。 首先,连接板子与PC,进入Dos命令行,进入C:\Program Files\STMicroelectronics\STM32 ST-LINK Utility\ST-LINK Utility目录下,执行S
[单片机]
<font color='red'>STM32</font> ST-LINK Utility无法下载的处理方法
STM32低功耗模式---停机和待机模式
已经在STM32F103下测试: 进入低功耗模式先执行: RCC_APB2PeriphResetCmd(0X01FC,DISABLE); //复位所有IO口, 端口全设置为高阻态,最好外设时钟也关闭 停机模式: RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //使能PWR外设时钟 /*进入停机模式, 停机模式还有两个选择模式,一个是调节器电压,可选常规或低压,低压在停机模式下功耗会更低,但是唤醒响应速度会慢一些;令一个唤醒方式的选择,可选事件唤醒、外部中断唤醒, 其实两者均可。*/ PWR_EnterSTOPMode(PWR_Regulator
[单片机]
<font color='red'>STM32</font>低功耗模式---停机和待机模式
STM32按键检测之短按与长按方法
在电路设计中,我们经常需要读取外部的电平信号。比如,在项目中,我们需要通过按键来输入一些数据,那么就需要检测按键是否被按下。电平分为高电平读取和低电平读取,读取高电平,需要设置IO为下拉电阻输入模式,反之,设置IO为上拉电阻输入模式。 S4按下时,单片机IO为高电平,S1-S3按下时,为低电平。我们设置S1为上拉输入模式,S4为下拉输入模式。本节使用按键实现2个功能: S1短按一次,LED2点亮,S1再短按一次,LED2熄灭。S1长按,LED1点亮,S1再长按,LED1熄灭。这种方式可以用来实现短按调节菜单,长按保存参数。 S4短按一次,LED4点亮,S4再短按一次,LED4熄灭。S4长按且不松手,LED4闪烁。这种方式可以用
[单片机]
<font color='red'>STM32</font>按键检测之短按与长按方法
STM32新建keil工程具体步骤(详细)
1. 新建本地工程文件夹 在本地电脑上新建一个“工程模板”文件夹,在它之下再新建 6 个文件夹: 2.添加库文件到相应文件夹 把 ST 标准库必要的文件复制到工程模版对应文件夹的目录下 3.KEIL5新建工程 打开 KEIL5,新建一个工程,文件名自拟,工程放在Project目录下。 保存后弹出芯片选择,要根据自己芯片的型号提前按照PACK包,选择型号后点击OK。 随后弹出在线添加库文件,关闭即可。 4.添加组文件夹 在新建的工程中添加常用的文件夹,用来存放不同的文件。 5.添加文件 在新建的工程中添加这些文件,双击组文件夹就会出现添加文件的路径,然后选择文件即可。 6.配置魔术棒选
[单片机]
<font color='red'>STM32</font>新建keil工程具体步骤(详细)
STM32 波形
只有给外设开启了时钟, 才能操作和控制外设。 // 产生三角波 在上述代码基础上加入下面代码,其中部分代码替代 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Period = 0xF; TIM_TimeBaseStructure.TIM_Prescaler = 0xF; TIM_TimeBaseSt
[单片机]
STM32通用定时器的几个重要寄存器
1..自动装载寄存器部分实际上包含两个寄存器: 自动装载寄存器缓冲寄存器 和 自动装载寄存器影子寄存器 其中自动装载寄存器缓冲寄存器可以有ARPE位控制是否起作用: ARPE = 0 写 自动装载寄存器 时,数据直接写入到 自动装载寄存器缓冲寄存器 的同时,立即更新到 自动装载寄存器影子寄存器 ARPE = 1 写 自动装载寄存器 时,数据直接写入到 自动装载寄存器缓冲寄存器 的同时,只有更新事件发生的时候,才更新到 自动装载寄存器影子寄存器 2.预分频器控制寄存器也分为两部分: 预分频器缓冲寄存器 和 预分频器影子寄存器 当更新事件发生的时候, 预分频器缓冲寄存器 的内容更新到 预分频器影子寄存器中 3.UDIS位作用:
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved