STM32自学笔记

1位带操作

第一种位带操作

#define BITBAND_REG(Reg,Bit) (*((uint32_t volatile*)(0x42000000u + (((uint32_t)&(Reg) - (uint32_t)0x40000000u)<<5)+(((uint32_t)(Bit))<<2))))
#define D0	BITBAND_REG(GPIOF->ODR,9)
#define D1	BITBAND_REG(GPIOF->ODR,10)
#define D2	BITBAND_REG(GPIOE->ODR,13)
#define D3	BITBAND_REG(GPIOE->ODR,14)

参考文档

第二种位带操作

建立一个文件(.h)文件,输入如下代码,然后就可以通过位带操作IO口了,注意包含《"stm32f4xx.h"》

#define BITBAND(addr,bitnum) ((addr & 0xF0000000)+0x2000000+((addr	&	0xFFFFF)<<5)+(bitnum<<2))
#define	MEM_ADDR(addr)	*((volatile unsigned long *)(addr))
#define BIT_ADDR(addr,bitnum)	MEM_ADDR(BITBAND(addr,bitnum))


//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014     

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

写完之后,我们就可以通过位带来控制stm32的IO口,比如

#define LED0 PEout(9)
#define LED1 PEout(10)

但是在编写这个过程中遇到了一个问题,报错内容为cannot take the address of an rvalue of type 'int'

主要是输入错误,正确输入如下:

#define BITBAND(addr,bitnum) ((addr & 0xF0000000)+0x2000000+((addr	&	0xFFFFF)<<5)+(bitnum<<2))

错误输入如下

#define BITBAND(addr,bitnum) ((addr & 0xF0000000)+0x2000000+((addr +	&	0xFFFFF)<<5)+(bitnum<<2))

仔细对比,可参考正点原子资料了解详情:

正点原子资料下载

2、在STM32编程遇到的一些关键字

1、 #pragma

在编程过程中偶尔会遇见 如下所示的代码程序:

#if defined(__SUPPORT_SNAN__) && defined(_WANT_SNAN)
#pragma import(__use_snan)
#endif

通过查找可以发现其位于“stdio.h”文件中,应该属于C语言内容的一部分;通过查阅资料可得知,

pragma属于预处理命令,其解释如下

#pragma命令的作用是使编译程序发生器向编译程序发出各种命令。

#pragma命令的一般形式如下

#pragma	名字

这里“名字”就是调用#pragma的名字

听完解释貌似还是好迷一样,大概意思就是用户通过#pragma这个预处理命令告诉编译器如何处理数据,一般根据参数来设置,有如下一些参数:

参数名 功能
message 它能够在编译信息输出窗口输出响应的信息,这对于源代码信息的控制是非常重要的。使用方法为:
#ifdef _x86
#pragma message("_x86 macro activated!")
#endif
当我们定义了_x86这个宏后,应用程序在编译时就会在编译输出窗口显示_x86 macro activated!
code_seg 其使用格式为#pragma code_seg(["section-name"[,"section_class"]])
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候可以用到它。
once 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,注意,这条指令在VC6中已经有了,考虑兼容性并没有太多的使用它
hdrstop 表示编译头文件到此为止,后面的头文件不进行预编译。BCB可以编译头文件以加快链接的速度,但是如果所有头文件都进行预编译又可能占用太多的磁盘空间。所有使用这个选项排除一些头文件
startup 有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译,可以使用
#pragma startup指定编译优先级,如果使用#pragma package(smart_init),BCB就会根据优先级先后编译。
resource #pragma resource "**.dfm"表示把.dfm中的资源加入工程.dfm中包含窗体外观的定义
warning #pragma warning(disable:4507 34;once:4385;error:164),等价于
#pragma warning(disable:4507 34)//不显示4507和34号警告信息
#pragma warning(once : 4385) //4385号警告信息仅报告一次
#pragma warning(error:164) //把164号警告信息作为一次错误。
comment #pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中,常用的lib关键字,可以帮助我们连入一个库文件

参考博客

总之#pragma指令就是将一些命令按照自己需要告诉编译器,那可以回到如下指令

#if defined(__SUPPORT_SNAN__) && defined(_WANT_SNAN)
#pragma import(__use_snan)
#endif

这些是C语言stdio.h文件中包含的声明,但是没有打开内容其内容;所以也不清楚这些东西是什么,我们转而描述对这些内容的疑问的来源吧

#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR = (u8) ch;      
	return ch;
}
#endif

在正点原子提供的资料的串口程序中有这么一段,我们在学C语言时我们知道可以使用现成printf();函数,但是在STM32中并未有用于底层printf();函数,需要我们重新写,因此这里就对这些函数重新定义了。

#pragma import(__use_no_semihosting)

对于上面那个语句的含义在标准库函数的默认输出设备是显示器,要实现在串口或者LCD输出,必须重新定义标准库函数里的输入与输出设备相关的函数。还有一点就是因为printf()之类的函数,使用了半主机模式。使用标准库会导致程序无法运行,可以通过下面两种方法解决。

方法一:使用微库

因为使用微库,不会使用半主机模式,如果使用的是MDK,可以在工程属性的”Target“—>"Code Generation"中勾选”Use MicroLIB“这样处理后就可以使用printf();sprintf()函数了。

方法2:仍然使用标准库

可以继续使用标准库,不过要在主程序中添加如下代码:

#pragma import(__use_no_semihosting)  // 确保没有从 C 库链接使用半主机的函数
_sys_exit(int  x) //定义 _sys_exit() 以避免使用半主机模式
{
x = x;
}
struct __FILE  // 标准库需要的支持函数
{
int handle;
};
/* FILE is typedef ’ d in stdio.h. */
FILE __stdout;

为确保没有从C库链接使用半主机函数,因为不使用半主机,标准C库stdio.h中有些使用半主机函数必须重新写。在独立应用程序中,不太可能支持半主机操作。因此,必须确保应用程序中没有链接C库半主机函数。为确保没有从C库连接使用半主机的函数,必须导入符号_use_no_semihosting;可在工程任何C或者汇编语言源文件中执行此操作,操作过程如下:

//在C语言程序中,使用#pragma指令
#pragma import(__use_no_semihosting)
//在汇编语言中,使用IMPORT指令
IMPORT __use_no_semihosting

至此就解释了为啥要重写printf()函数,并且#pragma import(__use_no_semihosting)是什么意思;参考资料如下所述,如果失效,可直接搜索如下关键字:

半主机

#pragma import(__use_no_snmihosting)关于串口输入输出的重定义

STM32 串口 #pragma import(__use_no_semihosting)解析

原文地址:https://www.cnblogs.com/liyingji/p/14215347.html