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)是什么意思;参考资料如下所述,如果失效,可直接搜索如下关键字: