目录:
十、Systick
1、SysTick介绍
2、SysTick timer 工作分析
十一、端口
1、端口的配置
1)各种模式配置 2)STM32 端口复用与重映射(如USART Remap) 3)推挽输出和开漏输出的区别
2、相关库函数的查询(STM32F10x_StdPeriph_Driver_3.5.0(中文版))
1)使用官方固件库查询 2)使用“stm32f103_非常好的函数库”查询
3、配置代码的查询(STM32F10x_StdPeriph_Driver_3.5.0(中文版))
4、端口功能查询
5、关于GPIO的BSR和BSRR寄存器
1)GPIO的寄存器BSRR和BRR简介 2)使用方法
十二、大小端与MSB、LSB
1、STM32大小端
2、大小端判断方法与MSB、LSB
1)通过程序 2)通过编译器编译结果
3、MSB 与LSB
1)简述 2)高位先行MSB 、低位先行LSB
十三、I2C
十四、相关总结
十五、查找汇编指令
十六、KEIL MDK
1、数据类型
1)KELI MDK 数据类型定义 2)STM32数据类型 3)数据类型混合运算 4)数据类型转换规则 5)volatile、const关键词说明
2、把变量定义到指定地址
1)定位到flash中 2)定位到RAM中
十、Systick
1、SysTick介绍
2、 SysTick timer 工作分析
首先看STK_CTRL控制寄存器:寄存器内有4个位具有意义
第0位:ENABLE,Systick 使能位 (0:关闭Systick功能;1:开启Systick功能)
第1位:TICKINT,Systick 中断使能位 (0:关闭Systick中断;1:开启Systick中断)
第2位:CLKSOURCE,Systick时钟源选择 (0:使用HCLK/8 作为Systick时钟;1:使用HCLK作为Systick时钟)
第16位:COUNTFLAG,Systick计数比较标志,如果在上次读取本寄存器后,SysTick 已经数到了0,则该位为1。如果读取该位。
校准寄存器一般很少用。
(以上寄存器操作可以参看以下程序加以理解:)
static u8 fac_us;//us延时倍乘数
static u32 fac_ms;//ms延时倍乘数
* Function Name :Delay_Init
* Description : SYSTICK的时钟固定为HCLK时钟的1/8
* Input : SYSCLK:系统主频时钟(SYSCLK Mhz)
* Return : None
void Delay_Init(u8 SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);// select HCLK/8 as systick clock source.
fac_us=SYSCLK/8; //9
fac_ms=(u32)fac_us*1000;
}
* Function Name:delay_ms
* Description : SysTick计数值最大为24位,最大值为16777215,如果fac_us=9000,那么nms的最大值为1864(1864ms)
* Input : nus,注意nms的范围 nms<=1864
void delay_ms(u16 nms)
{
SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //Enable Systick Function and start counting
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待计数到0
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器
}
* Function Name :delay_us
* Description : SysTick计数值最大为24位,最大值为16777215,如果fac_us=72那么nus
的最大值为233016,如果fac_us=9那么nus的最大值为1864128(1864ms)
* Input : nus
* Return : None
void delay_us(u32 nus)
{
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL = 0;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //Enable Systick Function and start counting
while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待计数到0
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器
}
当我们需要精确延时时,就可以利用 SysTick timer实现,理论上它的最小计时单位为AHB的时钟周期,即1/72000000 秒,72分之一的微秒,足以满足大部分极端应用需求。本小节以实例讲解如何利用 SysTick进行精确延时。抢占优先级要设置最高。
中断分组M3内核的配置方法:
NVIC_SetPriority(SysTick_IRQn,NVIC_EncodePriority(NVIC_PRIORITY_GROUP_5,0,0)) //抢占优先级0,响应优先级0
NVIC_SetPriority(EXTI5_10_IRQn,NVIC_EncodePriority(NVIC_PRIORITY_GROUP_5,1,0))//抢占优先级1,响应优先级0
十一、端口
1、端口的配置
图11.1.1
1)各种模式配置
以下代码截自实际工程,详情移步:STM32单片机-输入捕获、FFT测频。
浮空输入_Input_Floating
带上拉输入_Input Pull-up
带下拉输入_Input Pull-down
模拟输入_Analog
void adc_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Pin =
GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin =
GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
开漏输出_Out Ope-drain,类似于51单片机的准双向口
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能PortD的外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
//PC11 PC12 推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC,GPIO_Pin_13|GPIO_Pin_14); //SDA SCL输出高
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_ResetBits(GPIOC,GPIO_Pin_15); //置低 芯片使能
}
推挽输出_Out Push-pull
复用功能的推挽输出_Alternate function open-drain
复用功能的开漏输出_Alternate function push-pull
-------------------------------------------
2)STM32 端口复用与重映射(如USART Remap)
(1)RCC_APB2Periph_AFIO时钟一般在什么时候下需要开启?
引脚复用,进行重映射时,需要开启AFIO时钟,包括输入管脚中断。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC |RCC_APB2Periph_ADC1 | RCC_APB2Periph_AFIO |RCC_APB2Periph_USART1, ENABLE );
//使能ADC1通道时钟,各个管脚时钟 就是使能USART1管脚复用
-------------
(2)以USART1的重映射为例
因为我要一个TFT_LCD屏的主控板,考虑到FSMC 我选用了STM32F103VCT6 型号的CPU,一不小心串口接到USART1上了。因为在调程序时才发现错了,没得办法,只能通过端口重映射来解决。
STM32上有很多I/O口,也有很多的内置外设像I2C、ADC、ISP、USART等,为了节省引出管脚,这些内置外设基本上是与I/O口共用管脚的,也就是I/O管脚的复用功能。但是STM32还有一特别之处就是:很多复用内置的外设的I/O引脚可以通过重映射功能,从不同的I/O管脚引出,即复用功能的引脚是可通过程序改变的。下面说说我的调试经历。
不知道是什么原因PCB制图时把串口接到USART1上了,当时也没在意,等我把USART测试程序写好烧进去硬件仿真时,串口给的是乱码,我当时就觉得奇怪。把程序检查了好几遍就是查不出问题来,以为是硬件有问题,但突然想到了STM有复用功能,心想会不会是这里有鬼?于是找来datasheet 一看,真相大白。
三个红框交汇处。STM32F103VCT6 这个CPU的USART1接的是PB6/PB7 但是上电初始化后默认功能并非是USART1。所以想要用串口功能,必须用端口重映射。
大家知道,STM32的单片机每个功能模块有自己的时钟系统,所以要想要调用STM32单片机的功能模块时,必须先配置对应时钟,然后才能去操作相应的功能模块.端口重映射也一样。如图示:
-------------
(3)重映射步骤
打开重映射时钟和USART重映射后的I/O口引脚时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);
I/O口重映射开启
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
配制重映射引脚, 这里只需配置重映射后的I/O,原来的不需要去配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
只需要这三步,串口就可以正常使用了。
最后总结一下:
简单的说STM32的IO有3个功能:一个是默认的,一个是复用,一个是重映射功能(这个其实也属于复用);
如果配置成复用,则将使用第2个功能;如果配置成复用,同时,相应的重映射配置了,则将使用第3个功能;
通常一个口的复用+重映射有好多,不止两个,这时候就看你使能哪个设备了(哪个被使能就用哪个);
开复用+使能设备+是否重映射就可以决定这个IO口到底使用哪个功能。
-------------
(4)为了优化64脚或100 脚封装的外设数目,可以把一些复用功能重新映射到其他引脚上。设置复用重映射和调试I/O 配置寄存器(AFIO_MAPR) 实现引脚的重新映射。
这时,复用功能不再映射到它们的原始分配上。需要用到外设的重映射功能时才需要使能AFIO的时钟。外部中断(EXTI)中与AFIO有关的寄存器是AFIO-EXTICR1、2、3,它们是用来选择EXTIx外部中断的输入脚之用。
举例:重映射USART2
USART2的TX/RX在PA.2/3,但是PA.2已经被Timer2的channel3使用。这时,如果还想使用USART2,但又不想影响Timer2的使用,这就需要把USART2的TX/RX重映射到PD.5/6。
映射库函数的调用过程:
//使能被重新映射到的I/O端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
//使能被重新映射的外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
//使能AFIO功能的时钟(勿忘!)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//进行重映射
GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE);
如在使用EXTI时,如果不启动RCC_APB2Periph_AFIO时钟,无法使用外部中断。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE);
-------------------------------------------
3)推挽输出和开漏输出的区别
推挽输出:
图11.1.2
GPIO引脚线路经过两个保护二极管后,向上流向“输入模式”结构,向下流向“输出模式”结构。先看输出模式部分,线路经过一个由P-MOS和N-MOS管组成的单元电路。这个结构使GPIO具有了“推挽输出”和“开漏输出”两种模式。
所谓的推挽输出模式,是根据这两个MOS管的工作方式来命名的。在该结构中输入高电平时,经过反向后,上方的P-MOS导通,下方的N-MOS关闭,对外输出高电平;而在该结构中输入低电平时,经过反向后,N-MOS管导通,P-MOS关闭,对外输出低电平。当引脚高低电平切换时,两个管子轮流导通,P管负责灌电流,N管负责拉电流,使其负载能力和开关速度都比普通的方式有很大的提高。推挽输出的低电平为0伏,高电平为3.3伏,图11.1.2是推挽输出模式时的等效电路。
开漏输出:
图11.1.3
而在开漏输出模式时,上方的P-MOS管完全不工作。若控制输出为0低电平,则P-MOS管关闭,N-MOS管导通,使输出接地,若控制输出为1 (它无法直接输出高电平)时,则P-MOS管和N-MOS管都关闭,所以引脚既不输出高电平,也不输出低电平,为高阻态。正常使用时必须外部接上拉电阻,参考图11.1.3中等效电路。它具有“线与”特性,也就是说,若有很多个开漏模式引脚连接到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整条线路都为低电平0伏。推挽输出模式一般应用在输出电平为0和3.3V且需要高速切换开关状态的场合。在STM32的应用中,除了必须用开漏模式的场合,都习惯使用推挽输出模式。
开漏输出一般应用在I2C、SMBUS通讯等需要“线与”功能的总线电路中。除此之外,还用在电平不匹配的场合,如需要输出5伏的高电平,就可以在外部接一个上拉电阻,上拉电源为5V,并且把GPIO设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出5V的电平。
2、相关库函数的查询
1)使用官方固件库查询
-------------------------------------------
2)使用“stm32f103_非常好的函数库”查询
3、配置代码的查询
STM32F10x_StdPeriph_Driver_3.5.0(中文版)
4、端口功能查询
在数据手册中查询端口功能,比如“STM32F103xCDE_DS_CH_V5.pdf”,FT表示5V容忍。
5、关于GPIO的BSR和BSRR寄存器
1)GPIO的寄存器BSRR和BRR简介
(1)端口 位设置/复位寄存器BSRR:(如果同时设置了BSy和BRy的对应位,BSy位起作用,低位优先)
位31:16 BRy: 清除端口x的位y (y = 0…15) 这些位只能写入并只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响(ODR=Output Data Register)
1:清除对应的ODRy位为0
位15:0 BSy: 设置端口x的位y (y = 0…15) 这些位只能写入并只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响
1:设置对应的ODRy位为1
(2)端口 位复位寄存器BRR:
位31:16 保留。
位15:0 BRy: 清除端口x的位y (y = 0…15) 这些位只能写入并只能以字(16位)的形式操作。
0:对对应的ODRy位不产生影响
1:清除对应的ODRy位为0
-------------------------------------------
2)使用方法
(1)混合改变
需要置1的端口对应的位,在低16位里置1
需要置0的端口对应的位,在高16位里置1,不改变的,都置0. 然后写寄存器BSRR
(2)改变引脚为低
需要置0的端口对应的位,在低16位里置1,然后写寄存器BRR
(3)改变引脚为高
需要置1的端口对应的位,在低16位里置1 高16为全0 然后写寄存器BSRR
另外就是,STM32的库,GPIO_SetBits,GPIO_ResetBits,可以对多个引脚操作的,就是把需要操作的引脚用“|”(或运算)。
最方便的还是自己直接写寄存器(方法1)。
第一步:第一你要用的IO口 比如说要用A口的高8位定义 GPIOA_USE=0xF0
第二步:写BSRR寄存器。 GPIOA->BSRR=data&&GPIOA_USE
第三步:写BRR寄存器。 GPIOA->BRR=(~data)&&GPIOA_USE
假设data为8位要写入的数据:
GPIO_SetBits(GPIOD, data & 0xff00);
GPIO_ResetBits(GPIOD, (~data & 0xff00));
也可以直接操作这两个寄存器:
GPIOD->BSRR = data & 0xff00;
GPIOD->BRR = ~data & 0xff00;
规则:
规则一、置GPIOD->BSRR低16位的某位为'1',则对应的I/O端口置'1';而置GPIOD->BSRR低16位的某位为'0',则对应的I/O端口不变。
规则二、置GPIOD->BSRR高16位的某位为'1',则对应的I/O端口置'0';而置GPIOD->BSRR高16位的某位为'0',则对应的I/O端口不变。
规则三、置GPIOD->BRR低16位的某位为'1',则对应的I/O端口置'0';而置GPIOD->BRR低16位的某位为'0',则对应的I/O端口不变。
例如:
1)要设置D0、D5、D10、D11为高,而保持其它I/O口不变,只需一行语句:
GPIOD->BSRR = 0x0C21;// 使用规则一
2)要设置D1、D3、D14、D15为低,而保持其它I/O口不变,只需一行语句:
GPIOD->BRR = 0xC00A; // 使用规则三
3)要同时设置D0、D5、D10、D11为高,设置D1、D3、D14、D15为低,而保持其它I/O口不变,也只需一行语句:
高16位 低16位
31 24 16 8 0
1100 0000 0000 1010 0000 1100 0010 0001
GPIOD->BSRR = 0xC00A0C21;// 使用规则一和规则二
GPIOD->BSRR = (0x00 ff 00 00) | data;//根据data改变低8位端口的值,高8位端口不变(保持高8位不变,低8位为0)
十二、大小端与MSB、LSB
1、STM32大小端
大端模式:低位字节存在高地址上,高位字节存在低地址上;
小端模式:高位字节存在高地址上,低位字节存在低地址上。
KEIL C51中,变量都是大端模式的;KEIL MDK中,变量是小端模式的,STM32属于小端模式。
以unsigned int value = 0x12345678
为例说明。
内存地址 | 0x00000001 | 0x00000002 | 0x00000003 | 0x00000004 |
---|---|---|---|---|
大端模式 | 0x12 | 0x34 | 0x56 | 0x78 |
小端模式 | 0x78 | 0x56 | 0x34 | 0x12 |
同样以unsigned int value = 0x12345678
为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]
来表示value
。
2、大小端判断方法
1)通过程序
同样以0x12345678为例。
int temp=1; //定义一个int型变量temp并赋值1
char *p=(char *)&temp; //&temp取地址强制转换为指针,定义一个字符型指针指向temp变量的起始地址
if(*p==1)
printf("小端");
else
printf("大端");
参照上述两个图可知,若为小端则起始地址处被放入1,否则被放入0。
第3行读出字符型指针指向的地址处的字符,即变量temp的第一个字节处的值,并通过此值判断大端和小端。
-------------------------------------------
2)通过编译器编译结果
3、MSB与LSB
1)简述
最低有效位(the Least Significant Bit:LSB)是指一个二进制数字中的第0位(即最低位),具有权值为2^0,可以用它来检测数的奇偶性。在大端模式中LSB最右边的位。
图12.2.3 无符号数149的二进制形式,蓝色为最高有效位
最低有效位代表二进制数中的最小的单位,可以用来指示数字很小的变化。LSB有时也指Least Significant Byte,指多字节序列中最小权重的字节。
最高有效位(the Most Significant Bit:MSB),是指一个n位二进制数字中的n-1位,具有最高的权值2^n-1。在大端模式中MSB指最左端的位。
图12.2.4 无符号数149的二进制形式,蓝色为最高有效位
对于有符号二进制数,负数采用反码或补码形式,此时MSB用来表示符号,MSB为1表示负数,0表示正数。MSB有时也指Most Significant Byte,指多字节序列中具有最大权重的字节。
所以0x12345678的最高有效字节就是0x12,最低有效字节就是0x78。
-------------------------------------------
2)高位先行MSB 、低位先行LSB
(1)串口传输是低位先行
UART在数据传输时,协议规定了数据传输必须是低位先行,看下面的时序图你就知道了。
-------------
(2)IIC传输是高位先行
IIC的数据和地址均以8位字节传输,MSB 在前。从图中可以清楚地看到:
-------------
(3)SPI上升沿发送、下降沿接收、高位先发送
十三、I2C
I2C协议详见“算法、协议、信号处理/SPI与I2C总线协议”。
十四、相关总结
十五、查找汇编指令
有关Cortex 内核的指令我们可以参考《CM3 权威指南 CnR2》第四章:指令集。剩下的 ARM 的汇编指令我们可以在 MDK->Help->Uvision Help 中搜索到,以 AREA 为例,检索如下:
十六、KEIL MDK
1、数据类型
1)KELI MDK 数据类型定义
char 占用 1 个字节
short int 占用 2 字节//注意这里!
int 占用 4 字节
long 占用 4 字节 //注意这里!
long int 占用 4 字节 //注意这里!
float 占用 4 字节
double 占用 8 字节即有如下宏定义
typedef unsigned char uint8; // 无符号 8 位字符型变量
typedef signed char int8; // 有符号 8 位字符型变量
typedef unsigned short uint16; // 无符号 16 位短整型变量
typedef signed short int16; // 有符号 16 位短整型变量
typedef unsigned int uint32; // 无符号 32 位整型变量
typedef signed int int32; // 有符号 32 位整型变量
typedef float fp32; // 单精度浮点数(32 位长度)
typedef double fp64; // 双精度浮点数(64 位长度)
注:C 语言中的种类数据:整型:int、short、long,实型:float、double 。其中,unsigned 为无符号, signed 有符号。
-------------------------------------------
2)STM32数据类型宏定义
typedef unsigned char u8;0~255 一字节
typedef signed char s8;-128~127
typedef volatile unsigned char vu8;
typedef volatile signed char vs8;
typedef unsigned char const uc8;
typedef signed char const sc8;
typedef volatile unsigned char const vuc8;
typedef volatile signed char const vsc8;
typedef unsigned short u16; 0~65535 两字节
typedef signed short s16; -32768~32767 两字节
typedef volatile unsigned short vu16;
typedef volatile signed short vs16;
typedef unsigned short const uc16;
typedef signed short const sc16;
typedef signed short const sc16;
typedef volatile unsigned short const vuc16;
typedef volatile signed short const vsc16;
typedef unsigned long u32; 0~(2^32-1)四字节
typedef signed long s32; /
typedef signed long const sc32;
typedef volatile unsigned long const vuc32;
typedef volatile signed long const vsc32;
-------------------------------------------
3)数据类型混合运算
不同类型数据的混合运算在C 语言中,不同类型的数据间是可以混合运算的。在进行运算时,不同类型的数据要先转换成同一类型,然后进行运算。转换的规则如下:
注意:箭头的方向只表示数据类型级别的高低,由低向高转换,这个转换过程一步到位。
-------------------------------------------
4)数据类型转换规则
各类数据类型的转换,分为两种方式:隐式(编译软件自动完成),显式(程序强制转换)
(1)隐式转换规则
字符必须先转换为整数(C 语言规定字符类型数据和整型数据之间可以通用) short 型转换为 int 型(同属于整型)float 型数据在运算时一律转换为双精度(double)型,以提高运算精度(同属于实型) 赋值时,一律是右部值转换为左部类型
注:当整型数据和双精度数据进行运算时,C 先将整型数据转换成双精度型数据,再进行运算,结果为双精度类型数据
当字符型数据和实型数据进行运算时,C 先将字符型数据转换成实型数据,然后进行计算,结果为实型数据
----------------------
(2)显式转换规则:例:(int)(x+y);
注:强制类型转换时,得到一个所需要的中间变量,原来变量的类型未发生变化。
-------------------------------------------
5)volatile、const关键词说明
(1)volatile :这个关键字,很多人只知道用,不知道其含义,有介绍解释是不易被编译器优化的。在 STM32 资料中解释加了易挥发的变量。这些解释都是含糊不清的。为此笔者专门查了下这个修饰关键字含义。
通俗的解释: 随时会改变,并被多函数调用可以加 volatile 修饰。
简称易变变量或易挥发变量。 表示这个变量的真的很容易变。
进阶解释: 加了这个 volatile 意义就是在每次取这个变量值的时候,要求不是取它上次在某个时候取的临时缓存变量(比如说暂存在某个寄存器中),而是直接到内存中取。
个人经验:
告诉编译器,volatile 定义的变量必须 RAM 变量,不能是寄存器变量,尤其是中断中用全局变量。
----------------------
(2)const: 在定义变量时候,如果加上关键词 const,则变量的值在程序运行期间不能改变, 当然不能再赋值了。这种变量称为常变量(constant variable)或是只读变量(read-only-variable,这样觉得更恰当)。
2、把变量定义到指定地址
1)定位到flash中
const u16 FlashVariable[128] __attribute__((at(0x08001000))) =
{0x1111,0x1111,0x1111,0x0111,0x0111,0x0111};//定位在flash中,其他flash补充为00
-------------------------------------------
2)定位到RAM中
u8 RAMVariable[64] __attribute__ ((at(0X20001000)))={0};
注意:
绝对定位不能用于函数的局域变量定义,局部变量是定义在栈区的,栈区由MDK自动分配、释放,不能定义为绝对地址,只能放在函数外定义。
一
路漫漫其修远兮,吾将上下而求索。觉得不错,动动发财的小手点个赞哦!