STM32第十一课(自建实用库,utility, HAL)

串口最常用的函数,就是printf,
STM32中,HAL库并没有实现基于串口的stdio库,我们自建一个基于USART的库,utility,在其中实现printf。
STM32中,我们基于HAL库和systick,实现一个delay库,在其中实现delay。

++++++++++++++++++++++++++++++++
如果要使用printf,那么就需要

#include "stdio.h"

使用标准C的标准IO库。
但是,在STM32开发时,涉及到一个问题,就是是否在target中勾选use microLIB的问题。
如果勾选,将有很多ISO C的特性不能被支持。
所以,通常是不勾选的。
但是在不勾选的情况下,如果要使用printf,就需要自己覆盖定义一些函数,结构体定义。

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

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

void _ttywrch(int ch)
{
	ch = ch;
}

首先是要声明,导入宏定义__use_no_semihosting,这样,标准C库中的那些使用半主模式的函数,将不会参与到链接过程中。
这时,就需要为那些不能参与链接的代码段覆盖定义。

Library reports error: __use_no_semihosting was requested, but _ttywrch was referenced

这里,覆盖定义了__FILE, __stdout, _sys_exit,_ttywrch这几个函数。

之后,我们需要覆盖定义fputc函数。将输出重定向到usart1上。

//重定义fputc函数 
//重写这个函数,重定向printf函数到串口
int fputc(int ch, FILE *f)
{ 	
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);//注意把&huart1改为自己的stm32使用的串口号
	return ch;
}
//重定向scanf函数到串口 意思就是说接受串口发过来的数据
int fgetc(FILE * F)
{
  HAL_UART_Receive (&huart1,&ch_r,1,0xffff);//接收
  return ch_r;
}

我们用到了HAL库,所以需要包含HAL库的头文件。
用到了FILE等结构体定义,所以需要包含标准IO头文件。

#include "stdio.h"
#include "stm32f4xx_hal.h"

注意,printf是一个打印字符串的函数,从串口发出的每个byte,都代表一个char,
所以,如果我们用HAL_UART_Transmit直接发送,发送的是原始的hex数据,
例如0xbb,串口直接发送一个byte的0xbb,
但是通过printf发送时,如果发送的是%x选项,串口发送的是两个字节的0x62+0x62。

++++++++++++++++++++++++++++++++++++++
SysTick定时器的使用主要有
HAL_SYSTICK_Config()函数和HAL_SYSTICK_CLKSourceConfig()函数。
这些函数都是在HAL_InitTick()函数中被调用的。
cubemx会生成这部分代码。

HAL库中,定义了全局变量uwTick,用来记录当前时刻的Tick计数值。
ISR中,会调用HAL_IncTick函数,更新uwTick。

__IO uint32_t uwTick;

__weak void HAL_IncTick(void)
{
  	uwTick += uwTickFreq;
}

void SysTick_Handler(void)
{
  	HAL_IncTick();
  	HAL_SYSTICK_IRQHandler();
}

__weak uint32_t HAL_GetTick(void)
{
  return uwTick;
}

HAL_GetTick函数,用来获取当前uwTick值。

HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT;  /* 1KHz */
typedef enum
{
  HAL_TICK_FREQ_10HZ         = 100U,
  HAL_TICK_FREQ_100HZ        = 10U,
  HAL_TICK_FREQ_1KHZ         = 1U,
  HAL_TICK_FREQ_DEFAULT      = HAL_TICK_FREQ_1KHZ
} HAL_TickFreqTypeDef;

从中,可以看出uwTick的更新速度,
cubemx生成的代码,设置了systick为1ms中断一次。

我们设计的delay函数,属于自旋类型的延时,始终占用CPU,自旋耗时,不做别的事。
我们可以利用HAL_GetTick函数,来实现ms级的耗时,
通过不断读取uwTick的值,获取ms级自旋延时。

__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while ((HAL_GetTick() - tickstart) < wait);
}

可见,HAL_Delay函数,正是这个ms级别的自旋延时函数的范例。
我们可以直接使用HAL_Delay函数。

我们需要实现的,是delay_us函数。
首先,我们使用了HAL库,所以要包含HAL库的头文件。

#include "stm32f4xx_hal.h"

systick是1ms才触发一次中断,所以,要实现us级别的自旋延迟,我们只能通过读取TICK的寄存器的方式来实现。
来看看cubemx是如何设置TICK的,

if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
    return HAL_ERROR;
}

由全局变量SystemCoreClock/1000,设置了TICK的计数值,所以,当计数溢出时,TICK过了SystemCoreClock/1000个计数时钟,这就是1ms。

HAL库中,已经定义了SysTick,这是一个TICK的句柄,
我们不修改TICK的配置时,只需要使用SysTick->VAL读取当前的TICK中的CNT值即可。
TICK是一个递减计数器,这一点要注意。
另外,我们需要在代码中,处理CNT回卷的情况。

#include "stm32f4xx_hal.h"

static uint32_t fac_us=0;							//us延时倍乘数

//延时nus
//nus为要延时的us数.	
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)	 
void delay_us(uint32_t nus)
{		
	uint32_t ticks;
	uint32_t told,tnow,tcnt=0;
	uint32_t reload=SysTick->LOAD;				//LOAD的值
    fac_us=SystemCoreClock/1000/1000;	    	 
	ticks=nus*fac_us;
                               //需要的节拍数 
	tnow=SysTick->VAL;        				//刚进入时的计数器值
	while(1)
	{
        told=tnow;
		tnow=SysTick->VAL;
    
        if(tnow<=told)
            tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.
        else 
            tcnt+=reload-tnow+told;	    
			       
		if(tcnt>=ticks) break;			//时间超过/等于要延迟的时间,则退出.  
	}
}


在while(1)中,自旋延迟,反复读取SysTick->VAL的值,
在每一次读取了VAL的值的时候,记录下当前的VAL值。
采用移动窗口法。
这里,判断上一次登记的VAL值和本次登记的VAL值之间的跨度,即为本轮迭代消耗掉的时间,将这个时间,累加到耗时计数器中。
如果耗时超过需要的计数值,那么就可以退出自旋了。
+++++++++++++++++++++++++++++++++++++++++++++

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值