【13】STM32·HAL库-正点原子SYSTEM文件夹 | SysTick工作原理、寄存器介绍 | printf函数使用、重定向

目录

1.sys文件夹介绍(掌握)

  下面函数都是以sys_开头,定义在sys.c中。正点原子函数现阶段命名规则如果是在led.c中,则以led_开头。在F7/H7系列中会存在Cache配置函数,I-Cache中存储指令,D-Cache中存储数据。

2.deley文件夹介绍(掌握)

2.1deley文件夹函数简介

2.2SysTick工作原理

  SysTick,即系统滴答定时器,包含在M3/4/7内核里面,核心是一个24位的递减计数器(最大计数值为224=16777216)。当计数器减至0时,证明延时成功,则让COUNTFLAG置1,并将重装载寄存器中的值赋给计数器,重装载值可以自己设置,取值范围是从0开始0~16777215。

  每次VAL到0时,VAL自动从LOAD重载,开始新一轮递减计数。

2.3SysTick寄存器介绍

  SysTick控制及状态寄存器(CTRL)(摘自:Cortex M3权威指南(中文).pdf)。其中,CLKSOURCE并不是时钟源选择位,而是配置分频系数。

SysTick重装载数值寄存器(LOAD)(摘自:Cortex M3权威指南(中文).pdf),LOAD中的值会重装载到VAL寄存器中。

SysTick当前数值寄存器(VAL) (摘自:Cortex M3权威指南(中文).pdf)

2.4delay_init()函数(F1)

  形参sysclk为系统时钟,单位是M,比如在F1系列中系统时钟为72MHz,则填入72。

  下列代码第一行是设置系统滴答定时器的状态控制寄存器为0,在进行dellay_init()函数之前可能会调用HAL库的初始化函数,可以将系统滴答定时器的中断以及其他设置配置好,这里需要按照我们自己的意愿来设置,所以需要将HAL库设置的清0,不会干扰后面的配置;第二行是调用HAL库的函数来选择系统滴答定时器时钟源分频系数,这里选择8分频,也就是将CTRL寄存器的位CLKSOURCE置0;第三行是定义全局变量,作为1us时基的来源,如果系统滴答定时器的计数频率为1MHz,1秒钟计数1000 000次,计数一次用1/1000 000次,F1系列的系统时钟为72Mhz,系统滴答定时器进行8分频,系统滴答定时器真正计数频率为9Mhz,用sysclk除以8得到9Mhz,得到1us需要计数多少次。1/1000 000s=1us=g_fac_us ×1/9000 000,其中g_fac_us 为达到1us需要计数的次数。

csharp 复制代码
void delay_init(uint16_t sysclk) 
{ 
	SysTick->CTRL = 0; 
	HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8); 
	g_fac_us = sysclk / 8; 
}

2.5delay_us()函数(F1)

  在9MHz的计数频率上得到1us,需要进行计数9次,其中g_fac_us为9,使用变量temp来判断滴答定时器是否在工作,位16是判断计数是否完成,如果计数未完成则为0。

csharp 复制代码
void delay_us(uint32_t nus) 
{ 
	uint32_t temp; 
	SysTick->LOAD = nus * g_fac_us; 	/* 时间加载 */ 
	SysTick->VAL = 0x00; 			/* 清空计数器 */ 
	SysTick->CTRL |= 1 << 0 ; 		/* 开始倒数 */ 
	do 
	{ 
		temp = SysTick->CTRL; 
	} while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */

 
	SysTick->CTRL &= ~(1 << 0) ; 		/* 关闭SYSTICK */ 
	SysTick->VAL = 0X00; 			/* 清空计数器 */ 
}

2.6delay_ms()函数(F1)

  毫秒延时函数是利用us延时函数来实现的,那么就需要知道微秒延时函数的,所能延时的最大us数,F1系统时钟为72Mhz,经过8分频得到滴答定时器时钟9Mhz计数频率,计一个数为1/9000 000,可以计数224,则最大为1/9000 000 ×224≈1.864s,这是没有考虑超频,如果超频到128Mhz,经过8分频为16Mhz,1/16000 000×224≈1.048576s。那么如果延时需要超过1ms,则可以调用多次delay_us()函数,如果不超过1ms,可以直接使用delay_us()函数。

  代码第一行首先对1000取整数,将整数部分赋值给repeat 用于1s延时,小数部分赋值给remain用于小于1s的延时,用remain乘以1000是因为ms到us是相差1000倍。。

csharp 复制代码
void delay_ms(uint16_t nms) 
{ 
	uint32_t repeat = nms / 1000;	/* 这里用1000,是考虑到可能有超频应用, 
							    	 * 比如128Mhz的时候, delay_us最大只能延时1048576us
								 */ 
	uint32_t remain = nms % 1000; 
	while (repeat) 
	{ 
		delay_us(1000 * 1000); 	/* 利用delay_us 实现 1000ms 延时 */ 
		repeat--; 
	} 
	if (remain) 
	{ 
		delay_us(remain * 1000); 	/* 利用delay_us, 把尾数延时(remain ms)给做了 */ 
	} 
}

3.usart文件夹介绍(掌握)

3.1printf函数输出流程

  如果要使用printf()函数,必须包含stdio.h头文件,用工使用printf()函数,然后自动调用C标准库钟内容,最终会调用fputc()函数,此函数与硬件相关,通过屏幕或者串口来输出内容。

3.2printf的使用

  1. printf("字符串\r\n");使用\r\n实现换行,有些操作系统钟只用\n即可,为了兼容不同的操作系统推荐使用\r\n来实现换行。
csharp 复制代码
printf("Hello World!\r\n");
  1. printf("输出控制符",输出参数);%d是输出十进制数。
csharp 复制代码
uint32_t  temp = 10;
printf("%d\r\n", temp);          /* %d是输出控制符,temp是输出参数 */
  1. printf("输出控制符1输出控制符2...",输出参数1,输出参数2,...);%x以十六进制形式输出,则输出5A。
csharp 复制代码
uint32_t  temp1 = 5;   
uint32_t  temp2 = 10;
printf("%d%x\r\n", temp1,temp2);  
  1. printf("非输出控制符 输出控制符 非输出控制符",输出参数);
csharp 复制代码
uint32_t  temp = 10;   
printf("temp=  %d  收到over\r\n", temp);  
  1. 如何输出%、\和双引号
csharp 复制代码
printf("%% \r\n");
printf("\\\r\n");
printf("\"\"\r\n");

3.2.1常用输出控制符表

3.2.2常用转义字符表

3.3printf函数支持

  1. 避免使用半主机模式:两种方法:微库法、代码法
  2. 实现fputc函数实现单个字符输出

3.3.1半主机模式简介

  用于 ARM 目标的一种机制,可将来自应用程序代码的输入/输出请求传送至运行调试器的主机。简单说,就是通过仿真器实现开发板在电脑上的输入和输出,一般我们不使用半主机模式。具体半主机模式的介绍可以查看参考链接

3.3.2微库法

  在魔术棒->Target选项卡,勾选【Use Micro LIB】,即可避免半主机模式。

3.3.3代码法

  1个预处理、 2个定义、3个函数。

1.#pragma import(__use_no_semihosting),确保不从C库中使用半主机函数;

2.定义:__FILE结构体,避免HAL库某些情况下报错;

3.定义: FILE __stdout,避免编译报错;

4.实现:_ttywrch、_sys_exit和_sys_command_string等三个函数。
AC5和AC6不使用半主机模式稍有差异,详见源码

3.3.4微库法VS代码法

  推荐使用代码法,正点原子源码已做好。

csharp 复制代码
/******************************************************************************************/
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */

#if 1

#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART_UX->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    USART_UX->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif

3.3.5实现fputc函数

  在fputc函数中,第一行等待上一个字符发送完成,也就是检查串口状态寄存器SR的位6是否为1,为1则发送成功;第二行是将要发送的字符写入到串口的数据寄存器DR。如果注释掉第一行,print()函数发送的数据会乱码,因为fputc()函数是实现一个字符的输出,printf()输出很多个字符时,注释掉第一行代码将不再等待上一字符发送完成,将会一直发送叠加,导致乱码。使用微库法时,不能屏蔽掉fputc函数,只需要屏蔽1个预处理、 2个定义、3个函数。

csharp 复制代码
/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((USART_UX->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    USART_UX->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif

4.总结(了解)

相关推荐
z落落几秒前
C# 抽象类(abstract)
java·开发语言·c#
国产电子元器件11 分钟前
电流检测信号漂移问题分析
单片机·嵌入式硬件
YangWeiminPHD18 分钟前
单片机AI边缘计算发展之路:从M0的开局到三足鼎立的智能革命
人工智能·单片机·边缘计算
梁朝辉28 分钟前
筛选运算放大器时对于开环增益这一项参数怎么看?
嵌入式硬件·硬件工程
Sean_VIP40 分钟前
FreeRTOS项目程序框架介绍(五)
笔记·stm32
zlinear数据采集卡1 小时前
单点接地设计电路深度解析:从理论原理到ZLinear采集卡的低噪声实战
c语言·单片机·嵌入式硬件·fpga开发
嵌入式小站1 小时前
STM32 零基础可移植教程 15:ADC 多通道扫描,读取三路 PWM 的平均电压
stm32·单片机·嵌入式硬件
unityのkiven1 小时前
工作分享1(26.5.27):基于栈实现全局返回逻辑通用架构设计(适配异步 + 确认弹窗)
游戏·unity·c#·客户端架构
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 26 天:SPI通信协议基础、主从模式、速度特点
单片机·嵌入式硬件
湉湉家的小虎子2 小时前
【科普贴】浅谈UFS接口——偏硬件解析
驱动开发·嵌入式硬件·fpga开发·硬件工程