KEIL Study logs

来源:互联网 发布:制作条形码的软件 编辑:程序博客网 时间:2024/06/05 09:34

T0:在Keil调试STM32代码usart.c时,提示如下错误:

..\SYSTEM\usart\usart.c(48): error: #260-D: explicit type is missing (“int” assumed)
_sys_exit(int x)
..\SYSTEM\usart\usart.c: 0 warnings, 1 error

分析:

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

定义 _sys_exit(int x) 以避免使用半主机模式,函数没有返回类型,假定其返回类型为int,可以写为void类型函数:
void _sys_exit(int x) ,
否则编译器会默认为返回int类型,故会出现上述警告。

解决办法:应该写成void _sys_exit(int x)

T1:在Keil仿真STM32代码main.c调试USART1输出时,第一次仿真调试串口无输出,退出断点调试重新进入OK。

T2、主函数执行LED.c需要调用到LED.h库文件,因此必须:

1、 函数首先要在#include里声明;
2、 工程配置菜单 C/C++ 选项 Include Paths必须添加对应 .h文件的根目录
Note:Keil只会在一级目录里查找,所以Path一定要定位到最后一级子目录

T3 、使用USART下载,通过GPIO D13、D14推挽输出置高控制D1、D3灯常亮,LED.c所用到的结构体和typedef类型别名库函数或类型定义:

//////////////////Stm32f10x.h中定义:
* @brief General Purpose I/O
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;

//////////////////Stm32f10x_gpio.h中定义:
GPIO_InitTypeDef;
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIOMode_TypeDef /
}GPIO_InitTypeDef;

据此可通过以下步骤对GPIO进行初始化操作,
1. 先在APP程序里定义GPIO_InitStructure变量;
2. 然后先使能GPIO对应端口时钟以后,即可通过库函数里Define的宏定义变量值对GPIO_PIN、GPIO_Mode、GPIO_Speed对结构体变量进行特定赋值;
或者调用以下void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct) 函数进行默认初始化
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct)
{
/* Reset GPIO init structure parameters values */
GPIO_InitStruct->GPIO_Pin = GPIO_Pin_All;
GPIO_InitStruct->GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStruct->GPIO_Mode = GPIO_Mode_IN_FLOATING;
}
3. 然后通过调用 *****************Stm32f10x_gpio.c 里的
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) 函数将以上赋值(or默认初始化值)的GPIO_InitStruct的值给到对应GPIO的PIN脚;
GPIO_InitStruct:通过一个GPIO_InitTypeDef的结构体指针里的GPIO_PIN、GPIO_Mode、GPIO_Speed值来初始化 GPIOx所指向的PIN脚。
GPIO_PIN信息在GPIO_InitStructure变量中已包含,所以只需要指定GPIOx端口

Eg1: GPIO_Init(GPIOD, &GPIO_InitStructure);

  1. 之后再通过调用void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)或
    void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
    设置GPIO_Pin为高或低!!!!!!!!!!!!!!!!!

T4、C语言基础

C语言支持如下6中位操作运算符
运算符 含义 运算符 含义
& 按位与 ~ 取反
| 按位或 << 左移
^ 按位异或 >> 右移

1) 不改变其他位的值的状况下,对某几个位进行设值。
这个场景单片机开发中经常使用,方法就是先对需要设置的位用&操作符进行清零操作,然后用|操作符设值。比如我要改变GPIOA的状态,可以先对寄存器的值进行&清零操作
GPIOA->CRL&=0XFFFFFF0F; //将第4-7位清0
然后再与需要设置的值进行|或运算
GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值

2) 移位操作提高代码的可读性。
移位操作在单片机开发中也非常重要,下面让我们看看固件库的GPIO初始化的函数里面的一行代码
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
这个操作就是将BSRR寄存器的第pinpos位设置为1,为什么要通过左移而不是直接设置一个固定的值呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第pinpos位设置为1。如果你写成
GPIOx->BSRR =0x0030;
这样的代码就不好看也不好重用了。
类似这样的代码很多:
GPIOA->ODR|=1<<5; //PA.5输出高,不改变其他位
这样我们一目了然,5告诉我们是第5位也就是第6个端口,1告诉我们是设置为1了。
3) ~取反操作使用技巧
SR寄存器的每一位都代表一个状态,某个时刻我们希望去设置某一位的值为0,同时其他位都保留为1,简单的作法是直接给寄存器设置一个值:
TIMx->SR=0xFFF7;
这样的作法设置第3位为0,但是这样的作法同样不好看,并且可读性很差。看看库函数代码中怎样使用的:
TIMx->SR = (uint16_t)~TIM_FLAG;
而TIM_FLAG是通过宏定义定义的值:

define TIM_FLAG_Update ((uint16_t)0x0001)

define TIM_FLAG_CC1 ((uint16_t)0x0002)

看这个应该很容易明白,可以直接从宏定义中看出TIM_FLAG_Update就是设置的第0位了,可读性非常强。
4.1.3 ifdef条件编译
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:

ifdef 标识符

程序段1

else

程序段2

endif

它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。其中#else部分也可以没有,即:

ifdef

程序段1

endif

这个条件编译在MDK里面是用得很多的,在stm32f10x.h这个头文件中经常会看到这样的语句:

ifdef STM32F10X_HD

大容量芯片需要的一些变量定义

end

而STM32F10X_HD则是我们通过#define来定义的。条件编译也是c语言的基础知识,这里也就点到为止吧。
4.1.4 extern变量申明
C语言中extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于extern申明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句:
extern u16 USART_RX_STA;
这个语句是申明USART_RX_STA变量在其他文件中已经定义了,在这里要使用到。所以,你肯定可以找到在某个地方有变量定义的语句:
u16 USART_RX_STA;
的出现。下面通过一个例子说明一下使用方法。
在Main.c定义的全局变量id,id的初始化都是在Main.c里面进行的。
Main.c文件
u8 id;//定义只允许一次
main()
{
id=1;
printf(“d%”,id);//id=1
test();
printf(“d%”,id);//id=2
}
但是我们希望在test.c的 changeId(void)函数中使用变量id,这个时候我们就需要在test.c里面去申明变量id是外部定义的了,因为如果不申明,变量id的作用域是到不了test.c文件中。看下面test.c中的代码:
extern u8 id;//申明变量id是在外部定义的,申明可以在很多个文件中进行
void test(void){
id=2;
}
在test.c中申明变量id在外部定义,然后在test.c中就可以使用变量id了。
对于extern申明函数在外部定义的应用,这里我们就不多讲解了。
4.1.5 typedef类型别名
typedef用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef在MDK用得最多的就是定义结构体的类型别名和枚举类型了。
struct _GPIO
{
__IO uint32_t CRL;
__IO uint32_t CRH;

};
定义了一个结构体GPIO,这样我们定义变量的方式为:
struct _GPIO GPIOA;//定义结构体变量GPIOA
但是这样很繁琐,MDK中有很多这样的结构体变量需要定义。这里我们可以为结体定义一个别名GPIO_TypeDef,这样我们就可以在其他地方通过别名GPIO_TypeDef来定义结构体变量了。方法如下:
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;

} GPIO_TypeDef;
Typedef为结构体定义一个别名GPIO_TypeDef,这样我们可以通过GPIO_TypeDef来定义结构体变量:
GPIO_TypeDef _GPIOA,_GPIOB;
这里的GPIO_TypeDef就跟struct _GPIO是等同的作用了。 这样是不是方便很多?

4.1.6 结构体
经常很多用户提到,他们对结构体使用不是很熟悉,但是MDK中太多地方使用结构体以及结构体指针,这让他们一下子摸不着头脑,学习STM32的积极性大大降低,其实结构体并不是那么复杂,这里我们稍微提一下结构体的一些知识,还有一些知识我们会在下一节的“寄存器地址名称映射分析”中讲到一些。
声明结构体类型:
Struct 结构体名{
成员列表;
}变量名列表;
例如:
Struct U_TYPE {
Int BaudRate
Int WordLength;
}usart1,usart2;
在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:
Struct 结构体名字 结构体变量列表 ;
例如:struct U_TYPE usart1,usart2;
结构体成员变量的引用方法是:
结构体变量名字.成员名
比如要引用usart1的成员BaudRate,方法是:usart1.BaudRate;
结构体指针变量定义也是一样的,跟其他变量没有啥区别。
例如:struct U_TYPE *usart3;//定义结构体指针变量usart1;
结构体指针成员变量引用方法是通过“->”符号实现,比如要访问usart3结构体指针指向的结构体的成员变量BaudRate,方法是:
Usart3->BaudRate;

结构体到底有什么作用呢?为什么要使用结构体呢?下面我们将简单的通过一个实例回答一下这个问题。
在我们单片机程序开发过程中,经常会遇到要初始化一个外设比如串口,它的初始化状态是由几个属性来决定的,比如串口号,波特率,极性,以及模式。对于这种情况,在我们没有学习结构体的时候,我们一般的方法是:
void USART_Init(u8 usartx,u32 u32 BaudRate,u8 parity,u8 mode);
这种方式是有效的同时在一定场合是可取的。但是试想,如果有一天,我们希望往这个函数里面再传入一个参数,那么势必我们需要修改这个函数的定义,重新加入字长这个入口参数。于是我们的定义被修改为:
void USART_Init (u8 usartx,u32 BaudRate, u8 parity,u8 mode,u8 wordlength );
但是如果我们这个函数的入口参数是随着开发不断的增多,那么是不是我们就要不断的修改函数的定义呢?这是不是给我们开发带来很多的麻烦?那又怎样解决这种情况呢?
这样如果我们使用到结构体就能解决这个问题了。我们可以在不改变入口参数的情况下,只需要改变结构体的成员变量,就可以达到上面改变入口参数的目的。
结构体就是将多个变量组合为一个有机的整体。上面的函数,BaudRate,wordlength,
Parity,mode,wordlength这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参数的,所以我们可以将他们通过定义一个结构体来组合在一个。MDK中是这样定义的:
typedef struct
{
uint32_t USART_BaudRate;
uint16_t USART_WordLength;
uint16_t USART_StopBits;
uint16_t USART_Parity;
uint16_t USART_Mode;
uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;
于是,我们在初始化串口的时候入口参数就可以是USART_InitTypeDef类型的变量或者指针变量了,MDK中是这样做的:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
这样,任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而不需要修改函数定义就可以达到修改入口参数同样的目的了。这样的好处是不用修改任何函数定义就可以达到增加变量的目的。