STM32 KEIL C99编程(一)使用指针修改const变量的探索

一、关于const变量

const变量常被理解为常变量,一般不可被修改。在CMSIS中,一般常用__I表示:

#define   __I     volatile const       /*!< Defines 'read only' permissions                 */

实际上在keil默认的C99编译器中const修饰的变量仍然是变量,只是不允许出现在左值中。它作为一个变量,不能用来定义数组的长度,同样,在很多情况下依然是可以被修改的。

在GCC或者VC下,const变量的特点可以参考这一篇文章,个人觉得写的比较全面。

你真的理解了const和volatile关键字么?(我看不一定)

二、使用const修饰的全局变量

代码如下(示例):

const int testfault = 1;
int main(void)
{
	int *p = (int*)&testfault;
	*p=10;
}

在VC中编译通过,但是运行报错!
在这里插入图片描述
那么在KEIL中呢?
首先我们看看map文件,找一下testfault所在的位置
在这里插入图片描述
可以看到该变量位于STM32的内部Flash中紧挨着最后一段Thumb Code。那么是不是意味着这个变量没法通过指针修改值,甚至运行时出现异常呢?我们一起来看指令
在这里插入图片描述在这里插入图片描述

C代码中52行这一句话对应MOVS和STR这两行指令,截图中当MOVS指令执行完毕以后,R1寄存器被填入了立即数10,我们再下一步执行STR指令。
在这里插入图片描述
执行完了,神马也没有发生!我们来检查一下memory里面的值
在这里插入图片描述
不仅testfault的值没有变,连*p值也没有变!值没变,程序没报错,指令有在执行说明也没有被编译器优化。

结论:使用const修饰的全局变量位于内部flash/ROM中紧挨着代码段,值不会被修改。和VC不同的是,STM32程序不会报错。

三、用结构体组织的全局变量中的成员使用const修饰

代码如下(示例):

/* pwm_globalvariable.c */
typedef struct
{ 
	__I uint16_t PWMValue;
	uint16_t AC_Freq16;
	float AC_Freq;
}PWM_VARI;

__IO PWM_VARI pwm_vari;

结构体中PWMValue成员被__IO修饰,即volatile const,我们还是先看看map文件
在这里插入图片描述
可以看到这个结构体变量的地址位置和其他的全局变量没什么不同,都是从0x20000010开始的(2000000~20000009存的是RCC的内容)。
在这里插入图片描述
这个成员的地址也没有什么特殊的,就是结构体的首地址。我们来试一下用指针修改它的值。
在这里插入图片描述
不出所料,成功修改!

四、在三的基础上,再加上static修饰

STM32的中断服务函数只能是 void function(void),所以全局变量往往是不可避免的。为了防止全局变量满天飞,也为了避免修改时数组越界,或者修改一半时触发中断等等导致全局变量被意外“污损”,这里对全局变量进行static修饰,防止其被其他c文件的函数直接修改,并设计get/set接口供其他C文件的函数访问。
pwm_globalvariable.h

/*  pwm_globalvariable.h 
	定义结构体类型*/
typedef struct
{ 
	__I uint16_t PWMValue;
	uint16_t AC_Freq16;
	float AC_Freq;
}PWM_VARI;

typedef struct
{ 
	uint16_t PWMValue;
	uint16_t AC_Freq16;
	float AC_Freq;
}USER_PWM_VARI;

/* get/set接口 */
void set_pwm_vari(USER_PWM_VARI *p);
void set_pwm_member_u16( USER_PWM_VARI *base, uint16_t *member );
void set_pwm_member_f( USER_PWM_VARI *base, float *member );

void get_pwm_vari(USER_PWM_VARI *p);
void get_pwm_member_u16( USER_PWM_VARI *base, uint16_t *member );
void get_pwm_member_f( USER_PWM_VARI *base, float *member );

pwm_globalvariable.c

/*  pwm_globalvariable.c 
	定义全局变量,带有static
	跨文件访问只能使用接口*/
#include "./vari/PWM_GlobalVariable.h"
__IO static PWM_VARI pwm_vari;

void set_pwm_vari(USER_PWM_VARI *p)
{
    pwm_vari.AC_Freq = p->AC_Freq;
    pwm_vari.AC_Freq16 = p->AC_Freq16;
}

void set_pwm_member_f( USER_PWM_VARI *base, float *member )
{
	float *cl;		
	cl = (float *) ((uint32_t)&pwm_vari + ((uint32_t)member - (uint32_t)base)); 
	*cl = *member;
}

void set_pwm_member_u16( USER_PWM_VARI *base, uint16_t *member )
{
	uint16_t *cl;		
	cl = (uint16_t *) ((uint32_t)&pwm_vari + ((uint32_t)member - (uint32_t)base));
	*cl = *member;
}

void get_pwm_vari(USER_PWM_VARI *p)
{
    p->PWMValue = pwm_vari.PWMValue;
    p->AC_Freq = pwm_vari.AC_Freq;
    p->AC_Freq16 = pwm_vari.AC_Freq16;
}

void get_pwm_member_f( USER_PWM_VARI *base, float *member )
{
	float *cl;		
	cl = (float *) ((uint32_t)&pwm_vari + ((uint32_t)member - (uint32_t)base));
	*member = *cl;
}

void get_pwm_member_u16( USER_PWM_VARI *base, uint16_t *member )
{
	uint16_t *cl;		
	cl = (uint16_t *) ((uint32_t)&pwm_vari + ((uint32_t)member - (uint32_t)base));
	*member = *cl;
}

h文件中设计了两个成员组织完全一样的结构体,唯一的区别就是用于保存全局变量的PWM_VARI中的第一个成员使用了__I修饰,而用于副本的USER_PWM_VARI则没有。采用__I修饰的成员一般都使用了DMA,因此设计时希望它禁止被软件修改故加上__I。那么,加上__I能够达到目的吗?我们尝试使用set_pwm_member_u16函数来修改pwm_vari.PWMValue(试验时禁用了DMA)。
在这里插入图片描述
在这里插入图片描述

其实和第三节的情况没什么变化,结构体依然被分配到了相同的地址。不同点在于使用了接口函数,而接口函数内部也是在使用指针操作。__I并没有起到防止变量被修改的效果,真正想要实现全局变量的IO权限,只能自己动手把get/set方法实现好。

五、改进的set方法

这里只是根据本文中用于DMA传输的变量全部布置在结构体头部的特点,编写了宏函数
#define CHECK_POINT_THEN_ASSIGN(checkP, mem),其中&(pwm_vari.AC_Freq16)为第一个非__I变量的地址用于判断修改位置,并不具备一般参考性,未来仍需改进。

#define CHECK_POINT_THEN_ASSIGN(checkP, mem)                                   \
	if((uint32_t)(checkP) >= (uint32_t)&(pwm_vari.AC_Freq16))                  \
		*(checkP) = *(mem);                                                    \
	else                                                                       \
		return;

void set_pwm_vari(USER_PWM_VARI *p)
{
    pwm_vari.AC_Freq = p->AC_Freq;
    pwm_vari.AC_Freq16 = p->AC_Freq16;
}

void set_pwm_member_f( USER_PWM_VARI *base, float *member )
{
	float *cl;		
	cl = (float *) ((uint32_t)&pwm_vari + ((uint32_t)member - (uint32_t)base)); 
	CHECK_POINT_THEN_ASSIGN(cl, member)
}

void set_pwm_member_u16( USER_PWM_VARI *base, uint16_t *member )
{
	uint16_t *cl;		
	cl = (uint16_t *) ((uint32_t)&pwm_vari + ((uint32_t)member - (uint32_t)base));
	CHECK_POINT_THEN_ASSIGN(cl, member)
}

在这里插入图片描述
指令执行到BCC以后跳转,不执行LDRH和STRH。防止了__I变量被修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值