1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#
SYSTEM文件夹由正点原子提供,里面包含了一系列系统底层核心驱动代码,目的是为了方便读者快速构建自己的工程。本章将介绍SYSTEM文件夹中的代码,也希望读者能够灵活地使用SYSTEM文件夹中提供的各种函数来快速构建工程,并应用到实际的项目中。
SYSTEM文件夹下包含了sys、delay和usart三个文件夹,分别包含了sys.s、delay.c和usart.c以及相关的头文件,这三个C源文件提供了系统时钟设置、延时和串口1调试等功能,通过这些功能能够起到快速移植和辅助开发的作用。
本章分为如下几个小节:
9.1 sys文件夹代码介绍
9.2 delay文件夹代码介绍
9.3 usart文件夹代码介绍
sys文件夹内包含了sys.c和sys.h两个文件,在sys.c文件中主要实现了如下几个函数:
/* 函数声明 */
void sys_nvic_set_vector_table(uint32_t baseaddr, uint32_t offset);
void sys_wfi_set(void);
void sys_intx_disable(void);
void sys_intx_enable(void);
void sys_msr_msp(uint32_t addr);
void sys_standby(void);
void sys_soft_reset(void);
uint8_t sys_apm32_clock_init( uint32_t plln,
uint32_t pllm,
uint32_t pllp,
uint32_t pllq);
这些函数主要作辅助开发的用途,都比较简单,看函数的注释也能明白函数的作用,最重要的sys_apm32_clock_init()配置系统时钟函数也在前面的章节中分析了,这里便不再对sys.c文件中的函数进行分析。
9.2 delay文件夹代码介绍
delay文件夹中包含了delay.c和delay.h两个文件,在delay.h文件中声明了以下一个函数:
/* 函数声明 */
void delay_init(uint16_t sysclk); /* 初始化延时功能 */
void delay_us(uint32_t nus); /* 微秒级延时 */
void delay_ms(uint16_t nms); /* 毫秒级延时 */
以上三个函数均在delay.c文件中实现,分别用于初始化延时功能、微秒级延时和毫秒级延时,该延时功能区分了裸机和操作系统的两种情况。
在裸机的情况下,该延时为阻塞延时,即CPU忙延时,说得再通俗一点就是“死等”,再延时期间,CPU只会不断地判断延时时间是否超时,而不会做其他任何事情。具体的实现是利用了SysTick,SysTick是一个24位的向下递减计数器,在初始化延时功能的时候,会配置SysTick的计数频率为系统时钟频率,即168MHz,也就是说SysTick的计数值减少168就耗时1微秒(不精确),如果要延时n微秒,那么只要让SysTick从n*168开始计数,当SysTick的计数值为0时,就说明延时了n微秒。而毫秒级延时就是循环延时多个1000微秒。
在操作系统的情况下,一般操作系统都会提供一个毫秒级的非阻塞延时,那么就可以使用操作系统提供的延时功能来进行毫秒级延时。而微秒级延时,则先将待延时的微秒数转换为毫秒数进行毫秒级延时,若还有余数,则使用裸机情况下的微秒级延时进行延时。
9.3 usart文件夹代码介绍
usart文件夹中包含了usart.c和usart.h两个文件,这两个文件主要用于驱动USART1进行串口数据的收发,方面调试。有关串口的相关知识,会在下文“串口通信实验”中具体讲解,本小节仅讲解printf函数的应用。
9.3.1 printf函数支持
在学习C语言时,可以通过printf函数在终端里显示信息,以此做一些简单的调试,但对于单片机来说,是否有办法实现使用printf进行辅助调试呢?
C标准库的printf为调试属性的函数,如果直接使用,会使单片机进入半主机模式(semihosting),这是一种调试模式,具体表现为直接下载代码后出现程序无法运行,但是在连接调试器进行Debug时程序反而能正常工作。半主机模式是ARM目标系统的一种机制,用于将输入或输出请求从应用程序代码通信到运行调试器的主机。例如,此机制可用于允许C标准库中的函数(如printf()和scanf())使用主机的屏幕和键盘,而不是在目标系统上设置屏幕和键盘。这很有用,因为开发硬件通常不具有最终系统的所有输入和输出设备,如屏幕、键盘等。半主机模式是通过一组定义好的软件指令(如SVC指令(以前称为SWI指令))来实现的,这些指令通过程序控制产生异常。应用程序调用相应的半主机调用,然后调试代理处理该异常。调试代理(这里的调试代理是仿真器)提供与主机之间的必需通信。也就是说使用半主机模式必须使用仿真器调试。
在独立环境下运行调试功能的函数,例如:printf函数,printf对字符ch处理后写入文件f,最后使用fputc将文件f输出到显示设备。对于PC端的设备,fputc通过复杂的源码,最终把字符显示到屏幕上。那我们需要做的,就是把printf调用的fputc函数重新实现,重定向fputc的输出端为串口,同时避免进入半主模式。
要避免半主机模式,主要有两种方式:一是使用MicroLIB,即微库;另一种方法是确保ARM应用程序中没有链接MicroLIB的半主机相关函数。
先说微库,MicroLIB是ARM为嵌入式设备开发的一套类似于标准C接口函数的精简代码库,用于替代默认C库,是专门针对专业嵌入式应用开发而设计的,特别适合那些对存储空间有特别要求的嵌入式应用程序,这些程序一般不在操作系统下运行。使用微库编写程序要注意其与默认C库之间存在的一些差异,如main()函数不能声明带参数,也无须返回;不支持stdio,除了无缓冲的stdin、stdout和syderr;微库不支持操作系统函数;微库不支持可选的单或两区存储模式;微库只提供分离的堆和栈两区存储模式等等,它裁减了很多函数,而且还有很多东西不支持。如果原来用标准库可以跑,选择MicroLIB后却突然不行了,是很常见的。与标准的C库不一样,微库重新实现了printf,使用微库的情况下就不会进入半主机模式了。MDK下使用微库的方法很简单,在“Target”下勾选“Use MicroLIB”即可,如下图所示:
图9.3.1.1 MDK下MicroLIB开启方法
在MDK中,不管是否使用半主机模式,使用printf、scanf、fopen和fread等函数都需要自行填充底层函数,以printf函数为例,需要自行实现fputc函数,若启用微库,在初始化和使能串口1之后,只需实现fputc函数即可将每个传给fputc函数的字符ch重定向到串口1,如果这时接上串口调试助手的话,可以看到串口的数据。实现的代码如下:
int fputc(int ch, FILE* f)
{
while (USART_ReadStatusFlag(USART1, USART_FLAG_TXC) == RESET);
USART_TxData(USART1, (uint16_t)ch);
return ch;
}
上面说到了微库的一些限制,使用时注意某些函数与标准库的区别就不会影响到我们代码的正常功能。如果不想使用微库,那就要用到我们提到的第二种方法:确保ARM应用程序中没有链接MicroLIB的半主机相关函数。只需在代码中添加不使用半主机的声明即可,对于AC5编译器版本,具体的实现代码如下:
#pragma import(__use_no_semihosting)
这样一来,MDK就不会把标准库的这部分函数链接进最终编译出的程序中了。
同时,由于缺少__FILE结构体,因此额外进行定义,如下所示:
struct __FILE
{
int handle;
};
如果用到原来半主机模式下的调试函数,需要重新实现它的一些依赖函数接口,对于printf函数需要实现的接口如下:
int _ttywrch(int ch)
{
ch = ch;
return ch;
}
void _sys_exit(int x)
{
x = x;
}
char *_sys_command_string(char *cmd, int len)
{
return NULL;
}
fputc的重定向和上文的一样,重定向到串口1即可,如果硬件资源允许,或者有特殊需求,也可以重定向到LCD或者其它串口。
int fputc(int ch, FILE* f)
{
while (USART_ReadStatusFlag(USART1, USART_FLAG_TXC) == RESET);
USART_TxData(USART1, (uint16_t)ch);
return ch;
}