STM32 CLion+HAL+MX+OPENOCD FOR ARM 常用库分享(一)

编程入门 行业动态 更新时间:2024-10-23 23:25:41

STM32 CLion+HAL+MX+OPENOCD FOR ARM <a href=https://www.elefans.com/category/jswz/34/1769776.html style=常用库分享(一)"/>

STM32 CLion+HAL+MX+OPENOCD FOR ARM 常用库分享(一)

适用于 HAL库 Clion STM32MX OPENOCD 的开发环境
及正点原子开发板–STM32F407ZGT6(探索者)
由正点原子HAL库改编,因为正点原子库适用于 [KEIL] IDE,而Clion所使用的ARM编译器不支持交叉编译等,导致使用正点原子库极容易报错,笔者因此改编自用,使用Clion在OPENOCD编译下无任何报错。
侵删

常用库

  • 一.Delay(有BUG)
    • 1.delay.h
    • 2.delay.c
  • 二.USART
    • 1.usart.h
    • 2.usart.c
  • 三.sys
    • 1.sys.h
    • 2.sys.c
  • 四.ReTarget
    • 1.retarger.h
    • 2.retarget.c
  • 五.正点原子库--USMART
    • 0.README.MD
    • 1.usmart.h
    • 2.usmart.c
    • 3.usmart_config.c
    • 4.usmart_str.h
    • 5.usmart_str.c
  • 六.正点原子库--外设库--DHT11
    • 1.dht11.h
    • 2.dht11.c
  • 七,自用HAL_Delay库
    • 1.HAL_Delay.h
    • 2.HAL_Delay.c
  • 八. 自用DHT22库
    • 1.DHT22.h
    • 2.DHT22.c

一.Delay(有BUG)

1.delay.h

#ifndef _DELAY_H
#define _DELAY_H
#include "sys.h"
//  
//大家一定要关注正点原子@ALIENTEK
//技术论坛:www.openedv
// void delay_init(u8 SYSCLK);
void delay_ms(u16 ms);
void delay_us(u32 us);
#endif

2.delay.c

#include "delay.h"
#include "sys.h"
// 	 
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//  
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//使用SysTick的普通计数模式对延迟进行管理(支持ucosii/ucosiii)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv
//创建日期:2017/4/6
//版本:V1.1
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved
//********************************************************************************
//修改说明
// static u32 fac_us=0;							//us延时倍乘数#if SYSTEM_SUPPORT_OS		static u16 fac_ms=0;				        //ms延时倍乘数,在os下,代表每个节拍的ms数
#endif#if SYSTEM_SUPPORT_OS							//如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
//当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
//首先是3个宏定义:
//delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
//delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
//delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
//然后是3个函数:
//delay_osschedlock:用于锁定OS任务调度,禁止调度
//delay_osschedunlock:用于解锁OS任务调度,重新开启调度
//delay_ostimedly:用于OS延时,可以引起任务调度.//本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
//支持UCOSII
#ifdef 	OS_CRITICAL_METHOD						//OS_CRITICAL_METHOD定义了,说明要支持UCOSII				
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OS_TICKS_PER_SEC	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNesting		//中断嵌套级别,即中断嵌套次数
#endif//支持UCOSIII
#ifdef 	CPU_CFG_CRITICAL_METHOD					//CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII	
#define delay_osrunning		OSRunning			//OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec	OSCfg_TickRate_Hz	//OS时钟节拍,即每秒调度次数
#define delay_osintnesting 	OSIntNestingCtr		//中断嵌套级别,即中断嵌套次数
#endif//us级延时时,关闭任务调度(防止打断us级延迟)
void delay_osschedlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD   			//使用UCOSIIIOS_ERR err; OSSchedLock(&err);						//UCOSIII的方式,禁止调度,防止打断us延时
#else										//否则UCOSIIOSSchedLock();							//UCOSII的方式,禁止调度,防止打断us延时
#endif
}//us级延时时,恢复任务调度
void delay_osschedunlock(void)
{	
#ifdef CPU_CFG_CRITICAL_METHOD   			//使用UCOSIIIOS_ERR err; OSSchedUnlock(&err);					//UCOSIII的方式,恢复调度
#else										//否则UCOSIIOSSchedUnlock();						//UCOSII的方式,恢复调度
#endif
}//调用OS自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHODOS_ERR err; OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err); //UCOSIII延时采用周期模式
#elseOSTimeDly(ticks);						    //UCOSII延时
#endif 
}//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{	HAL_IncTick();if(delay_osrunning==1)					//OS开始跑了,才执行正常的调度处理{OSIntEnter();						//进入中断OSTimeTick();       				//调用ucos的时钟服务程序               OSIntExit();       	 				//触发任务切换软中断}
}
#endif//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.u32 reload;
#endifHAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLKfac_us=SYSCLK;						//不论是否使用OS,fac_us都需要使用
#if SYSTEM_SUPPORT_OS 						//如果需要支持OS.reload=SYSCLK;					    //每秒钟的计数次数 单位为K	   reload*=1000000/delay_ostickspersec;	//根据delay_ostickspersec设定溢出时间//reload为24位寄存器,最大值:16777216,在180M下,约合0.745s左右	fac_ms=1000/delay_ostickspersec;		//代表OS可以延时的最少单位	   SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断SysTick->LOAD=reload; 					//每1/OS_TICKS_PER_SEC秒中断一次	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}								    #if SYSTEM_SUPPORT_OS 						//如果需要支持OS.
//延时nus
//nus:要延时的us数.	
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)	    								   
void delay_us(u32 nus)
{		u32 ticks;u32 told,tnow,tcnt=0;u32 reload=SysTick->LOAD;				//LOAD的值	    	 ticks=nus*fac_us; 						//需要的节拍数 delay_osschedlock();					//阻止OS调度,防止打断us延时told=SysTick->VAL;        				//刚进入时的计数器值while(1){tnow=SysTick->VAL;	if(tnow!=told){	    if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.else tcnt+=reload-tnow+told;	    told=tnow;if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.}  };delay_osschedunlock();					//恢复OS调度											    
}  
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u16 nms)
{	if(delay_osrunning&&delay_osintnesting==0)//如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)	    {		 if(nms>=fac_ms)						//延时的时间大于OS的最少时间周期 { delay_ostimedly(nms/fac_ms);	//OS延时}nms%=fac_ms;						//OS已经无法提供这么小的延时了,采用普通方式延时    }delay_us((u32)(nms*1000));				//普通方式延时
}
#else  //不用ucos时//延时nus
//nus为要延时的us数.	
//nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)	 
void delay_us(u32 us)
{		u32 ticks;u32 told,tnow,tcnt=0;u32 reload=SysTick->LOAD;				//LOAD的值	    	 ticks=us*fac_us; 						//需要的节拍数told=SysTick->VAL;        				//刚进入时的计数器值while(1){tnow=SysTick->VAL;	if(tnow!=told){	    if(tnow<told)tcnt+=told-tnow;	//这里注意一下SYSTICK是一个递减的计数器就可以了.else tcnt+=reload-tnow+told;	    told=tnow;if(tcnt>=ticks)break;			//时间超过/等于要延迟的时间,则退出.}  };
}//延时nms
//nms:要延时的ms数
void delay_ms(u16 ms)
{u32 i;for(i=0;i<ms;i++) delay_us(1000);
}
#endif

二.USART

1.usart.h

#ifndef _USART_H
#define _USART_H
#include "sys.h"
#include "stdio.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//串口1初始化
//正点原子@ALIENTEK
//技术论坛:www.openedv.csom
//修改日期:2017/4/6
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 正点原子 2009-2019
//All rights reserved
//********************************************************************************
//V1.0修改说明
//
#define USART_REC_LEN  			200  	//定义最大接收字节数 200
#define EN_USART1_RX 			1		//使能(1)/禁止(0)串口1接收extern u8  USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符
extern u16 USART_RX_STA;         		//接收状态标记
extern UART_HandleTypeDef UART1_Handler; //UART句柄#define RXBUFFERSIZE   1 //缓存大小
extern u8 aRxBuffer[RXBUFFERSIZE];//HAL库USART接收Buffer//如果想串口中断接收,请不要注释以下宏定义
void uart_init(u32 bound);#endif

2.usart.c

在这里插入代码#include "usart.h"
#include "delay.h"
//
//如果使用os,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//os 使用
#endif
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//串口1初始化
//正点原子@ALIENTEK
//技术论坛:www.openedv
//修改日期:2017/4/6
//版本:V1.5
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.0修改说明
//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#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#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记u8 aRxBuffer[RXBUFFERSIZE];//HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; //UART句柄//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound)
{//UART 初始化设置UART1_Handler.Instance=USART1;					    //USART1UART1_Handler.Init.BaudRate=bound;				    //波特率UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B;   //字长为8位数据格式UART1_Handler.Init.StopBits=UART_STOPBITS_1;	    //一个停止位UART1_Handler.Init.Parity=UART_PARITY_NONE;		    //无奇偶校验位UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE;   //无硬件流控UART1_Handler.Init.Mode=UART_MODE_TX_RX;		    //收发模式HAL_UART_Init(&UART1_Handler);					    //HAL_UART_Init()会使能UART1HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);//该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量}//UART底层初始化,时钟使能,引脚配置,中断配置
//此函数会被HAL_UART_Init()调用
//huart:串口句柄void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{//GPIO端口设置GPIO_InitTypeDef GPIO_Initure;if(huart->Instance==USART1)//如果是串口1,进行串口1 MSP初始化{__HAL_RCC_GPIOA_CLK_ENABLE();			//使能GPIOA时钟__HAL_RCC_USART1_CLK_ENABLE();			//使能USART1时钟GPIO_Initure.Pin=GPIO_PIN_9;			//PA9GPIO_Initure.Mode=GPIO_MODE_AF_PP;		//复用推挽输出GPIO_Initure.Pull=GPIO_PULLUP;			//上拉GPIO_Initure.Speed=GPIO_SPEED_FAST;		//高速GPIO_Initure.Alternate=GPIO_AF7_USART1;	//复用为USART1HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA9GPIO_Initure.Pin=GPIO_PIN_10;			//PA10HAL_GPIO_Init(GPIOA,&GPIO_Initure);	   	//初始化PA10#if EN_USART1_RXHAL_NVIC_EnableIRQ(USART1_IRQn);				//使能USART1中断通道HAL_NVIC_SetPriority(USART1_IRQn,3,3);			//抢占优先级3,子优先级3
#endif}}void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart->Instance==USART1)//如果是串口1{if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(aRxBuffer[0]!=0x0a)USART_RX_STA=0;//接收错误,重新开始else USART_RX_STA|=0x8000;	//接收完成了}else //还没收到0X0D{if(aRxBuffer[0]==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收}}}}
}//串口1中断服务程序
void USART1_IRQHandler(void)
{u32 timeout=0;
#if SYSTEM_SUPPORT_OS	 	//使用OSOSIntEnter();
#endifHAL_UART_IRQHandler(&UART1_Handler);	//调用HAL库中断处理公用函数timeout=0;while (HAL_UART_GetState(&UART1_Handler) != HAL_UART_STATE_READY)//等待就绪{timeout++;超时处理if(timeout>HAL_MAX_DELAY) break;}timeout=0;while(HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE) != HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1{timeout++; //超时处理if(timeout>HAL_MAX_DELAY) break;}
#if SYSTEM_SUPPORT_OS	 	//使用OSOSIntExit();
#endif
}
#endif片

三.sys

1.sys.h

#ifndef _SYS_H
#define _SYS_H
#include "stm32f4xx.h"//0,不支持os
//1,支持os
#define SYSTEM_SUPPORT_OS		0		//定义系统文件夹是否支持OS
///
//定义一些常用的数据类型短关键字 
typedef int32_t  s32;
typedef int16_t s16;
typedef int8_t  s8;typedef const int32_t sc32;  
typedef const int16_t sc16;  
typedef const int8_t sc8;  typedef __IO int32_t  vs32;
typedef __IO int16_t  vs16;
typedef __IO int8_t   vs8;typedef __I int32_t vsc32;  
typedef __I int16_t vsc16; 
typedef __I int8_t vsc8;   typedef uint32_t  u32;
typedef uint16_t u16;
typedef uint8_t  u8;typedef const uint32_t uc32;  
typedef const uint16_t uc16;  
typedef const uint8_t uc8; typedef __IO uint32_t  vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t  vu8;typedef __I uint32_t vuc32;  
typedef __I uint16_t vuc16; 
typedef __I uint8_t vuc8;  //位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).M4同M3类似,只是寄存器地址变了.
//IO口操作宏定义
#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)  //输入void Error_Handler(void);
void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq);//时钟系统配置
//以下为汇编函数
void WFI_SET(void);		//执行WFI指令
void INTX_DISABLE(void);//关闭所有中断
void INTX_ENABLE(void);	//开启所有中断
void MSR_MSP(u32 addr);	//设置堆栈地址 
#endif

2.sys.c

#include "sys.h"//时钟系统配置函数
//Fvco=Fs*(plln/pllm);
//SYSCLK=Fvco/pllp=Fs*(plln/(pllm*pllp));
//Fusb=Fvco/pllq=Fs*(plln/(pllm*pllq));//Fvco:VCO频率
//SYSCLK:系统时钟频率
//Fusb:USB,SDIO,RNG等的时钟频率
//Fs:PLL输入时钟频率,可以是HSI,HSE等.
//plln:主PLL倍频系数(PLL倍频),取值范围:64~432.
//pllm:主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
//pllp:系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
//pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.//外部晶振为8M的时候,推荐值:plln=336,pllm=8,pllp=2,pllq=7.
//得到:Fvco=8*(336/8)=336Mhz
//     SYSCLK=336/2=168Mhz
//     Fusb=336/7=48Mhz
//返回值:0,成功;1,失败
void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{HAL_StatusTypeDef ret = HAL_OK;RCC_OscInitTypeDef RCC_OscInitStructure;RCC_ClkInitTypeDef RCC_ClkInitStructure;__HAL_RCC_PWR_CLK_ENABLE(); //使能PWR时钟//下面这个设置用来设置调压器输出电压级别,以便在器件未以最大频率工作//时使性能与功耗实现平衡。__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);//设置调压器输出电压级别1RCC_OscInitStructure.OscillatorType=RCC_OSCILLATORTYPE_HSE;    //时钟源为HSERCC_OscInitStructure.HSEState=RCC_HSE_ON;                      //打开HSERCC_OscInitStructure.PLL.PLLState=RCC_PLL_ON;//打开PLLRCC_OscInitStructure.PLL.PLLSource=RCC_PLLSOURCE_HSE;//PLL时钟源选择HSERCC_OscInitStructure.PLL.PLLM=pllm; //主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.RCC_OscInitStructure.PLL.PLLN=plln; //主PLL倍频系数(PLL倍频),取值范围:64~432.RCC_OscInitStructure.PLL.PLLP=pllp; //系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)RCC_OscInitStructure.PLL.PLLQ=pllq; //USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.ret=HAL_RCC_OscConfig(&RCC_OscInitStructure);//初始化if(ret!=HAL_OK) while(1);//选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2RCC_ClkInitStructure.ClockType=(RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2);RCC_ClkInitStructure.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;//设置系统时钟时钟源为PLLRCC_ClkInitStructure.AHBCLKDivider=RCC_SYSCLK_DIV1;//AHB分频系数为1RCC_ClkInitStructure.APB1CLKDivider=RCC_HCLK_DIV4; //APB1分频系数为4RCC_ClkInitStructure.APB2CLKDivider=RCC_HCLK_DIV2; //APB2分频系数为2ret=HAL_RCC_ClockConfig(&RCC_ClkInitStructure,FLASH_LATENCY_5);//同时设置FLASH延时周期为5WS,也就是6个CPU周期。if(ret!=HAL_OK) { Error_Handler(); };//STM32F405x/407x/415x/417x Z版本的器件支持预取功能if (HAL_GetREVID() == 0x1001){__HAL_FLASH_PREFETCH_BUFFER_ENABLE();  //使能flash预取}
}void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}
//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI  
void WFI_SET(void)
{
__asm ("WFI;");
}
//关闭所有中断(但是不包括fault和NMI中断)
void INTX_DISABLE(void)
{
__asm ("CPSID   I");
__asm ("BX      LR	");
}
//开启所有中断
void INTX_ENABLE(void)
{__asm ("CPSIE   I");__asm ("BX      LR  ");
}
//设置栈顶地址
//addr:栈顶地址
void MSR_MSP(u32 addr)
{
__asm ("MSR MSP, r0 ")	;		//set Main Stack value
__asm ("BX r14");
}

四.ReTarget

1.retarger.h

#ifndef _RETARGET_H__
#define _RETARGET_H__#include "stm32f4xx_hal.h"
#include <sys/stat.h>
#include <stdio.h>void RetargetInit(UART_HandleTypeDef *huart);int _isatty(int fd);int _write(int fd, char *ptr, int len);int _close(int fd);int _lseek(int fd, int ptr, int dir);int _read(int fd, char *ptr, int len);int _fstat(int fd, struct stat *st);#endif //#ifndef _RETARGET_H__

2.retarget.c

#include <_ansi.h>
#include <_syslist.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/times.h>
#include <retarget.h>
#include <stdint.h>#if !defined(OS_USE_SEMIHOSTING)#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2UART_HandleTypeDef *gHuart;void RetargetInit(UART_HandleTypeDef *huart)
{gHuart = huart;/* Disable I/O buffering for STDOUT stream, so that* chars are sent out as soon as they are printed. */setvbuf(stdout, NULL, _IONBF, 0);
}int _isatty(int fd)
{if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)return 1;errno = EBADF;return 0;
}int _write(int fd, char *ptr, int len)
{HAL_StatusTypeDef hstatus;if (fd == STDOUT_FILENO || fd == STDERR_FILENO){hstatus = HAL_UART_Transmit(gHuart, (uint8_t *)ptr, len, HAL_MAX_DELAY);if (hstatus == HAL_OK)return len;elsereturn EIO;}errno = EBADF;return -1;
}int _close(int fd)
{if (fd >= STDIN_FILENO && fd <= STDERR_FILENO)return 0;errno = EBADF;return -1;
}int _lseek(int fd, int ptr, int dir)
{(void)fd;(void)ptr;(void)dir;errno = EBADF;return -1;
}int _read(int fd, char *ptr, int len)
{HAL_StatusTypeDef hstatus;if (fd == STDIN_FILENO){hstatus = HAL_UART_Receive(gHuart, (uint8_t *)ptr, 1, HAL_MAX_DELAY);if (hstatus == HAL_OK)return 1;elsereturn EIO;}errno = EBADF;return -1;
}int _fstat(int fd, struct stat *st)
{if (fd >= STDIN_FILENO && fd <= STDERR_FILENO){st->st_mode = S_IFCHR;return 0;}errno = EBADF;return 0;
}#endif //#if !defined(OS_USE_SEMIHOSTING)

五.正点原子库–USMART

0.README.MD

USMART V3.1USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 它,你可以通过串口助手调用程
序里面的任何函数,并执行.因此,你可以随意更改函数的输入参数(支持数字(10/16进制)、字符串、
函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持函数返 回值显示.V2.1新增 
hex和dec两个指令.他们可以用于设置函数参数的显示格式.也可以用于数据的进制转换.
例如:
输入"hex 100"  会在串口调试助手上看到 HEX 0X64.
输入"dec 0X64" 会在串口调试助手上看到 DEC 100.升级说明
V1.4
增加了对参数为string类型的函数的支持.适用范围大大提高.
优化了内存占用,静态内存占用为79个字节@10个参数.动态适应数字及字符串长度
V2.0 
1,修改了list指令,打印函数的完整表达式.
2,增加了id指令,打印每个函数的入口地址.
3,修改了参数匹配,支持函数参数的调用(输入入口地址).
4,增加了函数名长度宏定义.	
V2.1 20110707		 
1,增加dec,hex两个指令,用于设置参数显示进制,及执行进制转换.
注:当dec,hex不带参数的时候,即设定显示参数进制.当后跟参数的时候,即执行进制转换.
如:"dec 0XFF" 则会将0XFF转为255,由串口返回.
如:"hex 100" 	则会将100转为0X64,由串口返回
2,新增usmart_get_cmdname函数,用于获取指令名字.
V2.2 20110726	
1,修正了void类型参数的参数统计错误.
2,修改数据显示格式默认为16进制.
V2.3 20110815
1,去掉了函数名后必须跟"("的限制.
2,修正了字符串参数中不能有"("的bug.
3,修改了函数默认显示参数格式的修改方式. 
V2.4 20110905
1,修改了usmart_get_cmdname函数,增加最大参数长度限制.避免了输入错误参数时的死机现象.
2,增加USMART_ENTIM2_SCAN宏定义,用于配置是否使用TIM2定时执行scan函数.
V2.5 20110930
1,修改usmart_init函数为void usmart_init(u8 sysclk),可以根据系统频率自动设定扫描时间.(固定100ms)
2,去掉了usmart_init函数中的uart_init函数,串口初始化必须在外部初始化,方便用户自行管理.
V2.6 20111009
1,增加了read_addr和write_addr两个函数.可以利用这两个函数读写内部任意地址(必须是有效地址).更加方便调试.
2,read_addr和write_addr两个函数可以通过设置USMART_USE_WRFUNS为来使能和关闭.
3,修改了usmart_strcmp,使其规范化.			  
V2.7 20111024
1,修正了返回值16进制显示时不换行的bug.
2,增加了函数是否有返回值的判断,如果没有返回值,则不会显示.有返回值时才显示其返回值.
V2.8 20111116
1,修正了list等不带参数的指令发送后可能导致死机的bug.
V2.9 20120917
1,修改了形如:void*xxx(void)类型函数不能识别的bug。
V3.0 20130425
1,新增了字符串参数对转义符的支持。
V3.1 20131120
1,增加runtime系统指令,可以用于统计函数执行时间.
用法:
发送:runtime 1 ,则开启函数执行时间统计功能
发送:runtime 0 ,则关闭函数执行时间统计功能
runtime统计功能,必须设置:USMART_ENTIMX_SCAN 为1,才可以使用!!正点原子@ALIENTEK技术论坛:www.openedv修改日期:2013/11/20Copyright(C) ALIENTEK 2011-2021

1.usmart.h

#ifndef __USMART_H
#define __USMART_H	  		  
#include "usmart_str.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板	   
//正点原子@ALIENTEK
//技术论坛:www.openedv 
//版本:V3.3
//版权所有,盗版必究。
//Copyright(C) 正点原子 2011-2021
//All rights reserved
//********************************************************************************
//升级说明
//V1.4
//增加了对参数为string类型的函数的支持.适用范围大大提高.
//优化了内存占用,静态内存占用为79个字节@10个参数.动态适应数字及字符串长度
//V2.0 
//1,修改了list指令,打印函数的完整表达式.
//2,增加了id指令,打印每个函数的入口地址.
//3,修改了参数匹配,支持函数参数的调用(输入入口地址).
//4,增加了函数名长度宏定义.	
//V2.1 20110707		 
//1,增加dec,hex两个指令,用于设置参数显示进制,及执行进制转换.
//注:当dec,hex不带参数的时候,即设定显示参数进制.当后跟参数的时候,即执行进制转换.
//如:"dec 0XFF" 则会将0XFF转为255,由串口返回.
//如:"hex 100" 	则会将100转为0X64,由串口返回
//2,新增usmart_get_cmdname函数,用于获取指令名字.
//V2.2 20110726	
//1,修正了void类型参数的参数统计错误.
//2,修改数据显示格式默认为16进制.
//V2.3 20110815
//1,去掉了函数名后必须跟"("的限制.
//2,修正了字符串参数中不能有"("的bug.
//3,修改了函数默认显示参数格式的修改方式. 
//V2.4 20110905
//1,修改了usmart_get_cmdname函数,增加最大参数长度限制.避免了输入错误参数时的死机现象.
//2,增加USMART_ENTIM2_SCAN宏定义,用于配置是否使用TIM2定时执行scan函数.
//V2.5 20110930
//1,修改usmart_init函数为void usmart_init(u8 sysclk),可以根据系统频率自动设定扫描时间.(固定100ms)
//2,去掉了usmart_init函数中的uart_init函数,串口初始化必须在外部初始化,方便用户自行管理.
//V2.6 20111009
//1,增加了read_addr和write_addr两个函数.可以利用这两个函数读写内部任意地址(必须是有效地址).更加方便调试.
//2,read_addr和write_addr两个函数可以通过设置USMART_USE_WRFUNS为来使能和关闭.
//3,修改了usmart_strcmp,使其规范化.			  
//V2.7 20111024
//1,修正了返回值16进制显示时不换行的bug.
//2,增加了函数是否有返回值的判断,如果没有返回值,则不会显示.有返回值时才显示其返回值.
//V2.8 20111116
//1,修正了list等不带参数的指令发送后可能导致死机的bug.
//V2.9 20120917
//1,修改了形如:void*xxx(void)类型函数不能识别的bug。
//V3.0 20130425
//1,新增了字符串参数对转义符的支持。
//V3.1 20131120
//1,增加runtime系统指令,可以用于统计函数执行时间.
//用法:
//发送:runtime 1 ,则开启函数执行时间统计功能
//发送:runtime 0 ,则关闭函数执行时间统计功能
///runtime统计功能,必须设置:USMART_ENTIMX_SCAN 为1,才可以使用!!
//V3.2 20140828
//1,修改usmart_get_aparm函数,加入+/-符号的支持
//2,修改usmart_str2num函数,支持负数转换
//V3.3 20160506
//修正usmart_exe函数在USMART_ENTIMX_SCAN为0的时候,报错的bug
///USMART资源占用情况@MDK 3.80A@2.0版本:
//FLASH:4K~K字节(通过USMART_USE_HELP和USMART_USE_WRFUNS设置)
//SRAM:72字节(最少的情况下)
//SRAM计算公式:   SRAM=PARM_LEN+72-4  其中PARM_LEN必须大于等于4.
//应该保证堆栈不小于100个字节.
用户配置参数	  
#define MAX_FNAME_LEN 		30	//函数名最大长度,应该设置为不小于最长函数名的长度。											   
#define MAX_PARM 			10	//最大为10个参数 ,修改此参数,必须修改usmart_exe与之对应.
#define PARM_LEN 			200	//所有参数之和的长度不超过PARM_LEN个字节,注意串口接收部分要与之对应(不小于PARM_LEN)#define USMART_ENTIMX_SCAN 	1	//使用TIM的定时中断来扫描SCAN函数,如果设置为0,需要自己实现隔一段时间扫描一次scan函数.//注意:如果要用runtime统计功能,必须设置USMART_ENTIMX_SCAN为1!!!!#define USMART_USE_HELP		1	//使用帮助,该值设为0,可以节省近700个字节,但是将导致无法显示帮助信息。
#define USMART_USE_WRFUNS	1	//使用读写函数,使能这里,可以读取任何地址的值,还可以写寄存器的值.
///END///#define USMART_OK 			0  //无错误
#define USMART_FUNCERR 		1  //函数错误
#define USMART_PARMERR 		2  //参数错误
#define USMART_PARMOVER 	3  //参数溢出
#define USMART_NOFUNCFIND 	4  //未找到匹配函数#define SP_TYPE_DEC      	0  //10进制参数显示
#define SP_TYPE_HEX       	1  //16进制参数显示//函数名列表	 
struct _m_usmart_nametab
{void* func;			//函数指针const u8* name;		//函数名(查找串)	 
};
//usmart控制管理器
struct _m_usmart_dev
{struct _m_usmart_nametab *funs;	//函数名指针void (*init)(u8);				//初始化u8 (*cmd_rec)(u8*str);			//识别函数名及参数void (*exe)(void); 				//执行 void (*scan)(void);             //扫描u8 fnum; 				  		//函数数量u8 pnum;                        //参数数量u8 id;							//函数idu8 sptype;						//参数显示类型(非字符串参数):0,10进制;1,16进制;u16 parmtype;					//参数的类型u8  plentbl[MAX_PARM];  		//每个参数的长度暂存表u8  parm[PARM_LEN];  			//函数的参数u8 runtimeflag;					//0,不统计函数执行时间;1,统计函数执行时间,注意:此功能必须在USMART_ENTIMX_SCAN使能的时候,才有用u32 runtime;					//运行时间,单位:0.1ms,最大延时时间为定时器CNT值的2倍*0.1ms
};
extern struct _m_usmart_nametab usmart_nametab[];	//在usmart_config.c里面定义
extern struct _m_usmart_dev usmart_dev;				//在usmart_config.c里面定义void usmart_init(u8 sysclk);//初始化
u8 usmart_cmd_rec(u8*str);	//识别
void usmart_exe(void);		//执行
void usmart_scan(void);     //扫描
u32 read_addr(u32 addr);	//读取指定地址的值
void write_addr(u32 addr,u32 val);//在指定地址写入指定的值
u32 usmart_get_runtime(void);	//获取运行时间
void usmart_reset_runtime(void);//复位运行时间#endif

2.usmart.c

#include "usmart.h"
#include "usart.h"
#include "sys.h"
#include "retarget.h"//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板	   
//正点原子@ALIENTEK
//技术论坛:www.openedv 
//版本:V3.1
//版权所有,盗版必究。
//Copyright(C) 正点原子 2011-2021
//All rights reserved
//********************************************************************************
//升级说明
//V1.4
//增加了对参数为string类型的函数的支持.适用范围大大提高.
//优化了内存占用,静态内存占用为79个字节@10个参数.动态适应数字及字符串长度
//V2.0 
//1,修改了list指令,打印函数的完整表达式.
//2,增加了id指令,打印每个函数的入口地址.
//3,修改了参数匹配,支持函数参数的调用(输入入口地址).
//4,增加了函数名长度宏定义.	
//V2.1 20110707		 
//1,增加dec,hex两个指令,用于设置参数显示进制,及执行进制转换.
//注:当dec,hex不带参数的时候,即设定显示参数进制.当后跟参数的时候,即执行进制转换.
//如:"dec 0XFF" 则会将0XFF转为255,由串口返回.
//如:"hex 100" 	则会将100转为0X64,由串口返回
//2,新增usmart_get_cmdname函数,用于获取指令名字.
//V2.2 20110726	
//1,修正了void类型参数的参数统计错误.
//2,修改数据显示格式默认为16进制.
//V2.3 20110815
//1,去掉了函数名后必须跟"("的限制.
//2,修正了字符串参数中不能有"("的bug.
//3,修改了函数默认显示参数格式的修改方式. 
//V2.4 20110905
//1,修改了usmart_get_cmdname函数,增加最大参数长度限制.避免了输入错误参数时的死机现象.
//2,增加USMART_ENTIM2_SCAN宏定义,用于配置是否使用TIM2定时执行scan函数.
//V2.5 20110930
//1,修改usmart_init函数为void usmart_init(u8 sysclk),可以根据系统频率自动设定扫描时间.(固定100ms)
//2,去掉了usmart_init函数中的uart_init函数,串口初始化必须在外部初始化,方便用户自行管理.
//V2.6 20111009
//1,增加了read_addr和write_addr两个函数.可以利用这两个函数读写内部任意地址(必须是有效地址).更加方便调试.
//2,read_addr和write_addr两个函数可以通过设置USMART_USE_WRFUNS为来使能和关闭.
//3,修改了usmart_strcmp,使其规范化.			  
//V2.7 20111024
//1,修正了返回值16进制显示时不换行的bug.
//2,增加了函数是否有返回值的判断,如果没有返回值,则不会显示.有返回值时才显示其返回值.
//V2.8 20111116
//1,修正了list等不带参数的指令发送后可能导致死机的bug.
//V2.9 20120917
//1,修改了形如:void*xxx(void)类型函数不能识别的bug。
//V3.0 20130425
//1,新增了字符串参数对转义符的支持。
//V3.1 20131120
//1,增加runtime系统指令,可以用于统计函数执行时间.
//用法:
//发送:runtime 1 ,则开启函数执行时间统计功能
//发送:runtime 0 ,则关闭函数执行时间统计功能
///runtime统计功能,必须设置:USMART_ENTIMX_SCAN 为1,才可以使用!!
//V3.2 20140828
//1,修改usmart_get_aparm函数,加入+/-符号的支持
//2,修改usmart_str2num函数,支持负数转换
//V3.3 20160506
//修正usmart_exe函数在USMART_ENTIMX_SCAN为0的时候,报错的bug
/
TIM_HandleTypeDef TIM4_Handler;      //定时器句柄 
//系统命令
u8 *sys_cmd_tab[]=
{"?","help","list","id","hex","dec","runtime",	   
};	    
//处理系统指令
//0,成功处理;其他,错误代码;
u8 usmart_sys_cmd_exe(u8 *str)
{u8 i;u8 sfname[MAX_FNAME_LEN];//存放本地函数名u8 pnum;u8 rval;u32 res;  res=usmart_get_cmdname(str,sfname,&i,MAX_FNAME_LEN);//得到指令及指令长度if(res)return USMART_FUNCERR;//错误的指令 str+=i;	 	 			    for(i=0;i<sizeof(sys_cmd_tab)/4;i++)//支持的系统指令{if(usmart_strcmp(sfname,sys_cmd_tab[i])==0)break;}switch(i){					   case 0:case 1://帮助指令printf("\r\n");
#if USMART_USE_HELP printf("------------------------USMART V3.3------------------------ \r\n");printf("    USMART是由ALIENTEK开发的一个灵巧的串口调试互交组件,通过 \r\n");printf("它,你可以通过串口助手调用程序里面的任何函数,并执行.因此,你可\r\n");printf("以随意更改函数的输入参数(支持数字(10/16进制,支持负数)、字符串\r\n"),printf("、函数入口地址等作为参数),单个函数最多支持10个输入参数,并支持\r\n"),  printf("函数返回值显示.支持参数显示进制设置功能,支持进制转换功能.\r\n");printf("技术支持:www.openedv\r\n");printf("USMART有7个系统命令(必须小写):\r\n");printf("?:      获取帮助信息\r\n");printf("help:   获取帮助信息\r\n");printf("list:   可用的函数列表\r\n\n");printf("id:     可用函数的ID列表\r\n\n");printf("hex:    参数16进制显示,后跟空格+数字即执行进制转换\r\n\n");printf("dec:    参数10进制显示,后跟空格+数字即执行进制转换\r\n\n");printf("runtime:1,开启函数运行计时;0,关闭函数运行计时;\r\n\n");printf("请按照程序编写格式输入函数名及参数并以回车键结束.\r\n");    printf("--------------------------ALIENTEK------------------------- \r\n");
#elseprintf("指令失效\r\n");
#endifbreak;case 2://查询指令printf("\r\n");printf("-------------------------函数清单--------------------------- \r\n");for(i=0;i<usmart_dev.fnum;i++)printf("%s\r\n",usmart_dev.funs[i].name);printf("\r\n");break;	 case 3://查询IDprintf("\r\n");printf("-------------------------函数 ID --------------------------- \r\n");for(i=0;i<usmart_dev.fnum;i++){usmart_get_fname((u8*)usmart_dev.funs[i].name,sfname,&pnum,&rval);//得到本地函数名 printf("%s id is:\r\n0X%08X\r\n",sfname,usmart_dev.funs[i].func); //显示ID}printf("\r\n");break;case 4://hex指令printf("\r\n");usmart_get_aparm(str,sfname,&i);if(i==0)//参数正常{i=usmart_str2num(sfname,&res);	   	//记录该参数	if(i==0)						  	//进制转换功能{printf("HEX:0X%X\r\n",res);	   	//转为16进制}else if(i!=4)return USMART_PARMERR;//参数错误.else 				   				//参数显示设定功能{printf("16进制参数显示!\r\n");usmart_dev.sptype=SP_TYPE_HEX;  }}else return USMART_PARMERR;			//参数错误.printf("\r\n"); break;case 5://dec指令printf("\r\n");usmart_get_aparm(str,sfname,&i);if(i==0)//参数正常{i=usmart_str2num(sfname,&res);	   	//记录该参数	if(i==0)						   	//进制转换功能{printf("DEC:%lu\r\n",res);	   	//转为10进制}else if(i!=4)return USMART_PARMERR;//参数错误.else 				   				//参数显示设定功能{printf("10进制参数显示!\r\n");usmart_dev.sptype=SP_TYPE_DEC;  }}else return USMART_PARMERR;			//参数错误. printf("\r\n"); break;	 case 6://runtime指令,设置是否显示函数执行时间printf("\r\n");usmart_get_aparm(str,sfname,&i);if(i==0)//参数正常{i=usmart_str2num(sfname,&res);	   		//记录该参数	if(i==0)						   		//读取指定地址数据功能{if(USMART_ENTIMX_SCAN==0)printf("\r\nError! \r\nTo EN RunTime function,Please set USMART_ENTIMX_SCAN = 1 first!\r\n");//报错else{usmart_dev.runtimeflag=res;if(usmart_dev.runtimeflag)printf("Run Time Calculation ON\r\n");else printf("Run Time Calculation OFF\r\n"); }}else return USMART_PARMERR;   			//未带参数,或者参数错误	 }else return USMART_PARMERR;				//参数错误. printf("\r\n"); break;	    default://非法指令return USMART_FUNCERR;}return 0;
}

//移植注意:本例是以stm32为例,如果要移植到其他mcu,请做相应修改.
//usmart_reset_runtime,清除函数运行时间,连同定时器的计数寄存器以及标志位一起清零.并设置重装载值为最大,以最大限度的延长计时时间.
//usmart_get_runtime,获取函数运行时间,通过读取CNT值获取,由于usmart是通过中断调用的函数,所以定时器中断不再有效,此时最大限度
//只能统计2次CNT的值,也就是清零后+溢出一次,当溢出超过2次,没法处理,所以最大延时,控制在:2*计数器CNT*0.1ms.对STM32来说,是:13.1s左右
//其他的:TIM4_IRQHandler和Timer4_Init,需要根据MCU特点自行修改.确保计数器计数频率为:10Khz即可.另外,定时器不要开启自动重装载功能!!#if USMART_ENTIMX_SCAN==1
//复位runtime
//需要根据所移植到的MCU的定时器参数进行修改
void usmart_reset_runtime(void)
{__HAL_TIM_CLEAR_FLAG(&TIM4_Handler,TIM_FLAG_UPDATE);//清除中断标志位 __HAL_TIM_SET_AUTORELOAD(&TIM4_Handler,0XFFFF);     //将重装载值设置到最大__HAL_TIM_SET_COUNTER(&TIM4_Handler,0);             //清空定时器的CNTusmart_dev.runtime=0;	
}
//获得runtime时间
//返回值:执行时间,单位:0.1ms,最大延时时间为定时器CNT值的2倍*0.1ms
//需要根据所移植到的MCU的定时器参数进行修改
u32 usmart_get_runtime(void)
{if(__HAL_TIM_GET_FLAG(&TIM4_Handler,TIM_FLAG_UPDATE)==SET)//在运行期间,产生了定时器溢出{usmart_dev.runtime+=0XFFFF;}usmart_dev.runtime+=__HAL_TIM_GET_COUNTER(&TIM4_Handler);return usmart_dev.runtime;		//返回计数值
}  
//下面这两个函数,非USMART函数,放到这里,仅仅方便移植. 
//定时器4中断服务程序	 
void TIM4_IRQHandler(void)
{ 		    		  			       if(__HAL_TIM_GET_IT_SOURCE(&TIM4_Handler,TIM_IT_UPDATE)==SET)//溢出中断{usmart_dev.scan();	//执行usmart扫描__HAL_TIM_SET_COUNTER(&TIM4_Handler,0);;    //清空定时器的CNT__HAL_TIM_SET_AUTORELOAD(&TIM4_Handler,100);//恢复原来的设置}__HAL_TIM_CLEAR_IT(&TIM4_Handler, TIM_IT_UPDATE);//清除中断标志位
}//使能定时器4,使能中断.
void Timer4_Init(u16 arr,u16 psc)
{ //定时器4__HAL_RCC_TIM4_CLK_ENABLE();HAL_NVIC_SetPriority(TIM4_IRQn,3,3);    //设置中断优先级,抢占优先级3,子优先级3HAL_NVIC_EnableIRQ(TIM4_IRQn);          //开启ITM4中断    TIM4_Handler.Instance=TIM4;                          //通用定时器4TIM4_Handler.Init.Prescaler=psc;                     //分频TIM4_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;    //向上计数器TIM4_Handler.Init.Period=arr;                        //自动装载值TIM4_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;HAL_TIM_Base_Init(&TIM4_Handler);HAL_TIM_Base_Start_IT(&TIM4_Handler); //使能定时器4和定时器4中断 					 
}#endif

//初始化串口控制器
//sysclk:系统时钟(Mhz)
void usmart_init(u8 sysclk)
{
#if USMART_ENTIMX_SCAN==1Timer4_Init(1000,(u32)sysclk*100-1);//分频,时钟为10K ,100ms中断一次,注意,计数频率必须为10Khz,以和runtime单位(0.1ms)同步.
#endifusmart_dev.sptype=1;	//十六进制显示参数
}		
//从str中获取函数名,id,及参数信息
//*str:字符串指针.
//返回值:0,识别成功;其他,错误代码.
u8 usmart_cmd_rec(u8*str) 
{u8 sta,i,rval;//状态	 u8 rpnum,spnum;u8 rfname[MAX_FNAME_LEN];//暂存空间,用于存放接收到的函数名  u8 sfname[MAX_FNAME_LEN];//存放本地函数名sta=usmart_get_fname(str,rfname,&rpnum,&rval);//得到接收到的数据的函数名及参数个数	  if(sta)return sta;//错误for(i=0;i<usmart_dev.fnum;i++){sta=usmart_get_fname((u8*)usmart_dev.funs[i].name,sfname,&spnum,&rval);//得到本地函数名及参数个数if(sta)return sta;//本地解析有误	  if(usmart_strcmp(sfname,rfname)==0)//相等{if(spnum>rpnum)return USMART_PARMERR;//参数错误(输入参数比源函数参数少)usmart_dev.id=i;//记录函数ID.break;//跳出.}	}if(i==usmart_dev.fnum)return USMART_NOFUNCFIND;	//未找到匹配的函数sta=usmart_get_fparam(str,&i);					//得到函数参数个数	if(sta)return sta;								//返回错误usmart_dev.pnum=i;								//参数个数记录return USMART_OK;
}
//usamrt执行函数
//该函数用于最终执行从串口收到的有效函数.
//最多支持10个参数的函数,更多的参数支持也很容易实现.不过用的很少.一般5个左右的参数的函数已经很少见了.
//该函数会在串口打印执行情况.以:"函数名(参数1,参数2...参数N)=返回值".的形式打印.
//当所执行的函数没有返回值的时候,所打印的返回值是一个无意义的数据.
void usmart_exe(void)
{u8 id,i;u32 res;		   u32 temp[MAX_PARM];//参数转换,使之支持了字符串 u8 sfname[MAX_FNAME_LEN];//存放本地函数名u8 pnum,rval;id=usmart_dev.id;if(id>=usmart_dev.fnum)return;//不执行.usmart_get_fname((u8*)usmart_dev.funs[id].name,sfname,&pnum,&rval);//得到本地函数名,及参数个数 printf("\r\n%s(",sfname);//输出正要执行的函数名for(i=0;i<pnum;i++)//输出参数{if(usmart_dev.parmtype&(1<<i))//参数是字符串{printf("%c",'"');			 printf("%s",usmart_dev.parm+usmart_get_parmpos(i));printf("%c",'"');temp[i]=(u32)&(usmart_dev.parm[usmart_get_parmpos(i)]);}else						  //参数是数字{temp[i]=*(u32*)(usmart_dev.parm+usmart_get_parmpos(i));if(usmart_dev.sptype==SP_TYPE_DEC)printf("%ld",temp[i]);//10进制参数显示else printf("0X%X",temp[i]);//16进制参数显示 	   }if(i!=pnum-1)printf(",");}printf(")");
#if USMART_ENTIMX_SCAN==1usmart_reset_runtime();	//计时器清零,开始计时
#endifswitch(usmart_dev.pnum){case 0://无参数(void类型)											  res=(*(u32(*)())usmart_dev.funs[id].func)();break;case 1://有1个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0]);break;case 2://有2个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1]);break;case 3://有3个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2]);break;case 4://有4个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3]);break;case 5://有5个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4]);break;case 6://有6个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\temp[5]);break;case 7://有7个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\temp[5],temp[6]);break;case 8://有8个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\temp[5],temp[6],temp[7]);break;case 9://有9个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\temp[5],temp[6],temp[7],temp[8]);break;case 10://有10个参数res=(*(u32(*)())usmart_dev.funs[id].func)(temp[0],temp[1],temp[2],temp[3],temp[4],\temp[5],temp[6],temp[7],temp[8],temp[9]);break;}
#if USMART_ENTIMX_SCAN==1usmart_get_runtime();//获取函数执行时间
#endifif(rval==1)//需要返回值.{if(usmart_dev.sptype==SP_TYPE_DEC)printf("=%lu;\r\n",res);//输出执行结果(10进制参数显示)else printf("=0X%X;\r\n",res);//输出执行结果(16进制参数显示)	   }else printf(";\r\n");		//不需要返回值,直接输出结束if(usmart_dev.runtimeflag)	//需要显示函数执行时间{ printf("Function Run Time:%d.%1dms\r\n",usmart_dev.runtime/10,usmart_dev.runtime%10);//打印函数执行时间 }	
}
//usmart扫描函数
//通过调用该函数,实现usmart的各个控制.该函数需要每隔一定时间被调用一次
//以及时执行从串口发过来的各个函数.
//本函数可以在中断里面调用,从而实现自动管理.
//如果非ALIENTEK用户,则USART_RX_STA和USART_RX_BUF[]需要用户自己实现
void usmart_scan(void)
{u8 sta,len;  if(USART_RX_STA&0x8000)//串口接收完成?{					   len=USART_RX_STA&0x3fff;	//得到此次接收到的数据长度USART_RX_BUF[len]='\0';	//在末尾加入结束符. sta=usmart_dev.cmd_rec(USART_RX_BUF);//得到函数各个信息if(sta==0)usmart_dev.exe();	//执行函数 else {  len=usmart_sys_cmd_exe(USART_RX_BUF);if(len!=USMART_FUNCERR)sta=len;if(sta){   switch(sta){case USMART_FUNCERR:printf("函数错误!\r\n");break;	case USMART_PARMERR:printf("参数错误!\r\n");   			break;				case USMART_PARMOVER:printf("参数太多!\r\n");   			break;		case USMART_NOFUNCFIND:printf("未找到匹配的函数!\r\n");   			break;		}}}USART_RX_STA=0;//状态寄存器清空	    }
}
#if USMART_USE_WRFUNS==1 	//如果使能了读写操作
//读取指定地址的值		 
u32 read_addr(u32 addr)
{return *(u32*)addr;//	
}
//在指定地址写入指定的值		 
void write_addr(u32 addr,u32 val)
{*(u32*)addr=val; 	
}
#endif

3.usmart_config.c

#include "usmart.h"
#include "usmart_str.h"
用户配置区///
//这下面要包含所用到的函数所申明的头文件(用户自己添加) 
#include "delay.h"	 	
#include "sys.h"extern void led_set(u8 sta);
extern void test_fun(void(*ledset)(u8),u8 sta);										  
//函数名列表初始化(用户自己添加)
//用户直接在这里输入要执行的函数名及其查找串
struct _m_usmart_nametab usmart_nametab[]=
{
#if USMART_USE_WRFUNS==1 	//如果使能了读写操作(void*)read_addr,"u32 read_addr(u32 addr)",(void*)write_addr,"void write_addr(u32 addr,u32 val)",	 
#endif		   (void*)delay_ms,"void delay_ms(u16 nms)",(void*)delay_us,"void delay_us(u32 nus)",	 				
};						  
///END///
/
//函数控制管理器初始化
//得到各个受控函数的名字
//得到函数总数量
struct _m_usmart_dev usmart_dev=
{usmart_nametab,usmart_init,usmart_cmd_rec,usmart_exe,usmart_scan,sizeof(usmart_nametab)/sizeof(struct _m_usmart_nametab),//函数数量0,	  	//参数数量0,	 	//函数ID1,		//参数显示类型,0,10进制;1,16进制0,		//参数类型.bitx:,0,数字;1,字符串	    0,	  	//每个参数的长度暂存表,需要MAX_PARM个0初始化0,		//函数的参数,需要PARM_LEN个0初始化
};   

4.usmart_str.h

#ifndef __USMART_STR_H
#define __USMART_STR_H	 
#include "sys.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板	   
//正点原子@ALIENTEK
//技术论坛:www.openedv 
//版本:V3.3
//版权所有,盗版必究。
//Copyright(C) 正点原子 2011-2021
//All rights reserved
//********************************************************************************
//升级说明
//V1.4
//增加了对参数为string类型的函数的支持.适用范围大大提高.
//优化了内存占用,静态内存占用为79个字节@10个参数.动态适应数字及字符串长度
//V2.0 
//1,修改了list指令,打印函数的完整表达式.
//2,增加了id指令,打印每个函数的入口地址.
//3,修改了参数匹配,支持函数参数的调用(输入入口地址).
//4,增加了函数名长度宏定义.	
//V2.1 20110707		 
//1,增加dec,hex两个指令,用于设置参数显示进制,及执行进制转换.
//注:当dec,hex不带参数的时候,即设定显示参数进制.当后跟参数的时候,即执行进制转换.
//如:"dec 0XFF" 则会将0XFF转为255,由串口返回.
//如:"hex 100" 	则会将100转为0X64,由串口返回
//2,新增usmart_get_cmdname函数,用于获取指令名字.
//V2.2 20110726	
//1,修正了void类型参数的参数统计错误.
//2,修改数据显示格式默认为16进制.
//V2.3 20110815
//1,去掉了函数名后必须跟"("的限制.
//2,修正了字符串参数中不能有"("的bug.
//3,修改了函数默认显示参数格式的修改方式. 
//V2.4 20110905
//1,修改了usmart_get_cmdname函数,增加最大参数长度限制.避免了输入错误参数时的死机现象.
//2,增加USMART_ENTIM2_SCAN宏定义,用于配置是否使用TIM2定时执行scan函数.
//V2.5 20110930
//1,修改usmart_init函数为void usmart_init(u8 sysclk),可以根据系统频率自动设定扫描时间.(固定100ms)
//2,去掉了usmart_init函数中的uart_init函数,串口初始化必须在外部初始化,方便用户自行管理.
//V2.6 20111009
//1,增加了read_addr和write_addr两个函数.可以利用这两个函数读写内部任意地址(必须是有效地址).更加方便调试.
//2,read_addr和write_addr两个函数可以通过设置USMART_USE_WRFUNS为来使能和关闭.
//3,修改了usmart_strcmp,使其规范化.			  
//V2.7 20111024
//1,修正了返回值16进制显示时不换行的bug.
//2,增加了函数是否有返回值的判断,如果没有返回值,则不会显示.有返回值时才显示其返回值.
//V2.8 20111116
//1,修正了list等不带参数的指令发送后可能导致死机的bug.
//V2.9 20120917
//1,修改了形如:void*xxx(void)类型函数不能识别的bug。
//V3.0 20130425
//1,新增了字符串参数对转义符的支持。
//V3.1 20131120
//1,增加runtime系统指令,可以用于统计函数执行时间.
//用法:
//发送:runtime 1 ,则开启函数执行时间统计功能
//发送:runtime 0 ,则关闭函数执行时间统计功能
///runtime统计功能,必须设置:USMART_ENTIMX_SCAN 为1,才可以使用!!
//V3.2 20140828
//1,修改usmart_get_aparm函数,加入+/-符号的支持
//2,修改usmart_str2num函数,支持负数转换
//V3.3 20160506
//修正usmart_exe函数在USMART_ENTIMX_SCAN为0的时候,报错的bug
/u8 usmart_get_parmpos(u8 num);						//得到某个参数在参数列里面的起始位置
u8 usmart_strcmp(u8*str1,u8 *str2);					//对比两个字符串是否相等
u32 usmart_pow(u8 m,u8 n);							//M^N次方
u8 usmart_str2num(u8*str,u32 *res);					//字符串转为数字
u8 usmart_get_cmdname(u8*str,u8*cmdname,u8 *nlen,u8 maxlen);//从str中得到指令名,并返回指令长度
u8 usmart_get_fname(u8*str,u8*fname,u8 *pnum,u8 *rval);		//从str中得到函数名
u8 usmart_get_aparm(u8 *str,u8 *fparm,u8 *ptype); 	//从str中得到一个函数参数
u8 usmart_get_fparam(u8*str,u8 *parn);  			//得到str中所有的函数参数.
#endif

5.usmart_str.c

#include "usmart_str.h"
#include "usmart.h"		   
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板	   
//正点原子@ALIENTEK
//技术论坛:www.openedv 
//版本:V3.3
//版权所有,盗版必究。
//Copyright(C) 正点原子 2011-2021
//All rights reserved
//********************************************************************************
//升级说明
//V1.4
//增加了对参数为string类型的函数的支持.适用范围大大提高.
//优化了内存占用,静态内存占用为79个字节@10个参数.动态适应数字及字符串长度
//V2.0 
//1,修改了list指令,打印函数的完整表达式.
//2,增加了id指令,打印每个函数的入口地址.
//3,修改了参数匹配,支持函数参数的调用(输入入口地址).
//4,增加了函数名长度宏定义.	
//V2.1 20110707		 
//1,增加dec,hex两个指令,用于设置参数显示进制,及执行进制转换.
//注:当dec,hex不带参数的时候,即设定显示参数进制.当后跟参数的时候,即执行进制转换.
//如:"dec 0XFF" 则会将0XFF转为255,由串口返回.
//如:"hex 100" 	则会将100转为0X64,由串口返回
//2,新增usmart_get_cmdname函数,用于获取指令名字.
//V2.2 20110726	
//1,修正了void类型参数的参数统计错误.
//2,修改数据显示格式默认为16进制.
//V2.3 20110815
//1,去掉了函数名后必须跟"("的限制.
//2,修正了字符串参数中不能有"("的bug.
//3,修改了函数默认显示参数格式的修改方式. 
//V2.4 20110905
//1,修改了usmart_get_cmdname函数,增加最大参数长度限制.避免了输入错误参数时的死机现象.
//2,增加USMART_ENTIM2_SCAN宏定义,用于配置是否使用TIM2定时执行scan函数.
//V2.5 20110930
//1,修改usmart_init函数为void usmart_init(u8 sysclk),可以根据系统频率自动设定扫描时间.(固定100ms)
//2,去掉了usmart_init函数中的uart_init函数,串口初始化必须在外部初始化,方便用户自行管理.
//V2.6 20111009
//1,增加了read_addr和write_addr两个函数.可以利用这两个函数读写内部任意地址(必须是有效地址).更加方便调试.
//2,read_addr和write_addr两个函数可以通过设置USMART_USE_WRFUNS为来使能和关闭.
//3,修改了usmart_strcmp,使其规范化.			  
//V2.7 20111024
//1,修正了返回值16进制显示时不换行的bug.
//2,增加了函数是否有返回值的判断,如果没有返回值,则不会显示.有返回值时才显示其返回值.
//V2.8 20111116
//1,修正了list等不带参数的指令发送后可能导致死机的bug.
//V2.9 20120917
//1,修改了形如:void*xxx(void)类型函数不能识别的bug。
//V3.0 20130425
//1,新增了字符串参数对转义符的支持。
//V3.1 20131120
//1,增加runtime系统指令,可以用于统计函数执行时间.
//用法:
//发送:runtime 1 ,则开启函数执行时间统计功能
//发送:runtime 0 ,则关闭函数执行时间统计功能
///runtime统计功能,必须设置:USMART_ENTIMX_SCAN 为1,才可以使用!!
//V3.2 20140828
//1,修改usmart_get_aparm函数,加入+/-符号的支持
//2,修改usmart_str2num函数,支持负数转换
//V3.3 20160506
//修正usmart_exe函数在USMART_ENTIMX_SCAN为0的时候,报错的bug
///对比字符串str1和str2
//*str1:字符串1指针
//*str2:字符串2指针
//返回值:0,相等;1,不相等;
u8 usmart_strcmp(u8 *str1,u8 *str2)
{while(1){if(*str1!=*str2)return 1;//不相等if(*str1=='\0')break;//对比完成了.str1++;str2++;}return 0;//两个字符串相等
}
//把str1的内容copy到str2
//*str1:字符串1指针
//*str2:字符串2指针			   
void usmart_strcopy(u8*str1,u8 *str2)
{while(1){										   *str2=*str1;	//拷贝if(*str1=='\0')break;//拷贝完成了.str1++;str2++;}
}
//得到字符串的长度(字节)
//*str:字符串指针
//返回值:字符串的长度		   
u8 usmart_strlen(u8*str)
{u8 len=0;while(1){							 if(*str=='\0')break;//拷贝完成了.len++;str++;}return len;
}
//m^n函数
//返回值:m^n次方
u32 usmart_pow(u8 m,u8 n)
{u32 result=1;	 while(n--)result*=m;    return result;
}	    
//把字符串转为数字
//支持16进制转换,但是16进制字母必须是大写的,且格式为以0X开头的.
//支持负数 
//*str:数字字符串指针
//*res:转换完的结果存放地址.
//返回值:0,成功转换完成.其他,错误代码.
//1,数据格式错误.2,16进制位数为0.3,起始格式错误.4,十进制位数为0.
u8 usmart_str2num(u8*str,u32 *res)
{u32 t;int tnum;u8 bnum=0;		//数字的位数u8 *p;		  u8 hexdec=10;	//默认为十进制数据u8 flag=0;		//0,没有符号标记;1,表示正数;2,表示负数.p=str;*res=0;//清零.while(1){if((*p<='9'&&*p>='0')||((*str=='-'||*str=='+')&&bnum==0)||(*p<='F'&&*p>='A')||(*p=='X'&&bnum==1))//参数合法{if(*p>='A')hexdec=16;	//字符串中存在字母,为16进制格式.if(*str=='-'){flag=2;str+=1;}//偏移掉符号else if(*str=='+'){flag=1;str+=1;}//偏移掉符号else bnum++;			//位数增加.}else if(*p=='\0')break;	//碰到结束符,退出.else return 1;				//不全是十进制或者16进制数据.p++; } p=str;			    //重新定位到字符串开始的地址.if(hexdec==16)		//16进制数据{if(bnum<3)return 2;			//位数小于3,直接退出.因为0X就占了2个,如果0X后面不跟数据,则该数据非法.if(*p=='0' && (*(p+1)=='X'))//必须以'0X'开头.{p+=2;	//偏移到数据起始地址.bnum-=2;//减去偏移量	 }else return 3;//起始头的格式不对}else if(bnum==0)return 4;//位数为0,直接退出.	  while(1){if(bnum)bnum--;if(*p<='9'&&*p>='0')t=*p-'0';	//得到数字的值else t=*p-'A'+10;				//得到A~F对应的值	    *res+=t*usmart_pow(hexdec,bnum);		   p++;if(*p=='\0')break;//数据都查完了.	}if(flag==2)//是负数?{	tnum=-*res; *res=tnum;}return 0;//成功转换
}
//得到指令名
//*str:源字符串
//*cmdname:指令名
//*nlen:指令名长度		
//maxlen:最大长度(做限制,指令不可能太长的)	
//返回值:0,成功;其他,失败.	  
u8 usmart_get_cmdname(u8*str,u8*cmdname,u8 *nlen,u8 maxlen)
{*nlen=0;while(*str!=' '&&*str!='\0') //找到空格或者结束符则认为结束了{*cmdname=*str;str++;cmdname++;(*nlen)++;//统计命令长度if(*nlen>=maxlen)return 1;//错误的指令}*cmdname='\0';//加入结束符return 0;//正常返回
}
//获取下一个字符(当中间有很多空格的时候,此函数直接忽略空格,找到空格之后的第一个字符)
//str:字符串指针	
//返回值:下一个字符
u8 usmart_search_nextc(u8* str)
{		   	 	str++;while(*str==' '&&str!='\0')str++;return *str;
} 
//从str中得到函数名
//*str:源字符串指针
//*fname:获取到的函数名字指针
//*pnum:函数的参数个数
//*rval:是否需要显示返回值(0,不需要;1,需要)
//返回值:0,成功;其他,错误代码.
u8 usmart_get_fname(u8*str,u8*fname,u8 *pnum,u8 *rval)
{u8 res;u8 fover=0;	  //括号深度u8 *strtemp;u8 offset=0;  u8 parmnum=0;u8 temp=1;u8 fpname[6];//void+X+'/0'u8 fplcnt=0; //第一个参数的长度计数器u8 pcnt=0;	 //参数计数器u8 nchar;//判断函数是否有返回值strtemp=str;while(*strtemp!='\0')//没有结束{if(*strtemp!=' '&&(pcnt&0X7F)<5)//最多记录5个字符{	if(pcnt==0)pcnt|=0X80;//置位最高位,标记开始接收返回值类型if(((pcnt&0x7f)==4)&&(*strtemp!='*'))break;//最后一个字符,必须是*fpname[pcnt&0x7f]=*strtemp;//记录函数的返回值类型pcnt++;}else if(pcnt==0X85)break;strtemp++; } if(pcnt)//接收完了{fpname[pcnt&0x7f]='\0';//加入结束符if(usmart_strcmp(fpname,"void")==0)*rval=0;//不需要返回值else *rval=1;							   //需要返回值pcnt=0;} res=0;strtemp=str;while(*strtemp!='('&&*strtemp!='\0') //此代码找到函数名的真正起始位置{  strtemp++;res++;if(*strtemp==' '||*strtemp=='*'){nchar=usmart_search_nextc(strtemp);		//获取下一个字符if(nchar!='('&&nchar!='*')offset=res;	//跳过空格和*号}}	 strtemp=str;if(offset)strtemp+=offset+1;//跳到函数名开始的地方	   res=0;nchar=0;//是否正在字符串里面的标志,0,不在字符串;1,在字符串;while(1){if(*strtemp==0){res=USMART_FUNCERR;//函数错误break;}else if(*strtemp=='('&&nchar==0)fover++;//括号深度增加一级	 else if(*strtemp==')'&&nchar==0){if(fover)fover--;else res=USMART_FUNCERR;//错误结束,没收到'('if(fover==0)break;//到末尾了,退出	    }else if(*strtemp=='"')nchar=!nchar;if(fover==0)//函数名还没接收完{if(*strtemp!=' ')//空格不属于函数名{*fname=*strtemp;//得到函数名fname++;}}else //已经接受完了函数名了.{if(*strtemp==','){temp=1;		//使能增加一个参数pcnt++;	}else if(*strtemp!=' '&&*strtemp!='('){if(pcnt==0&&fplcnt<5)		//当第一个参数来时,为了避免统计void类型的参数,必须做判断.{fpname[fplcnt]=*strtemp;//记录参数特征.fplcnt++;}temp++;	//得到有效参数(非空格)}if(fover==1&&temp==2){temp++;		//防止重复增加parmnum++; 	//参数增加一个}}strtemp++; 			}   if(parmnum==1)//只有1个参数.{fpname[fplcnt]='\0';//加入结束符if(usmart_strcmp(fpname,"void")==0)parmnum=0;//参数为void,表示没有参数.}*pnum=parmnum;	//记录参数个数*fname='\0';	//加入结束符return res;		//返回执行结果
}//从str中得到一个函数的参数
//*str:源字符串指针
//*fparm:参数字符串指针
//*ptype:参数类型 0,数字;1,字符串;0XFF,参数错误
//返回值:0,已经无参数了;其他,下一个参数的偏移量.
u8 usmart_get_aparm(u8 *str,u8 *fparm,u8 *ptype)
{u8 i=0;u8 enout=0;u8 type=0;//默认是数字u8 string=0; //标记str是否正在读while(1){		    if(*str==','&& string==0)enout=1;			//暂缓立即退出,目的是寻找下一个参数的起始地址if((*str==')'||*str=='\0')&&string==0)break;//立即退出标识符if(type==0)//默认是数字的{if((*str>='0' && *str<='9')||*str=='-'||*str=='+'||(*str>='a' && *str<='f')||(*str>='A' && *str<='F')||*str=='X'||*str=='x')//数字串检测{if(enout)break;					//找到了下一个参数,直接退出.if(*str>='a')*fparm=*str-0X20;	//小写转换为大写else *fparm=*str;		   		//小写或者数字保持不变fparm++;}else if(*str=='"')//找到字符串的开始标志{if(enout)break;//找到,后才找到",认为结束了.type=1;string=1;//登记STRING 正在读了}else if(*str!=' '&&*str!=',')//发现非法字符,参数错误{type=0XFF;break;}}else//string类{ if(*str=='"')string=0;if(enout)break;			//找到了下一个参数,直接退出.if(string)				//字符串正在读{	if(*str=='\\')		//遇到转义符(不复制转义符){ str++;			//偏移到转义符后面的字符,不管什么字符,直接COPYi++;}					*fparm=*str;		//小写或者数字保持不变fparm++;}	}i++;//偏移量增加str++;}*fparm='\0';	//加入结束符*ptype=type;	//返回参数类型return i;		//返回参数长度
}
//得到指定参数的起始地址
//num:第num个参数,范围0~9.
//返回值:该参数的起始地址
u8 usmart_get_parmpos(u8 num)
{u8 temp=0;u8 i;for(i=0;i<num;i++)temp+=usmart_dev.plentbl[i];return temp;
}
//从str中得到函数参数
//str:源字符串;
//parn:参数的多少.0表示无参数 void类型
//返回值:0,成功;其他,错误代码.
u8 usmart_get_fparam(u8*str,u8 *parn)
{	u8 i,type;  u32 res;u8 n=0;u8 len;u8 tstr[PARM_LEN+1];//字节长度的缓存,最多可以存放PARM_LEN个字符的字符串for(i=0;i<MAX_PARM;i++)usmart_dev.plentbl[i]=0;//清空参数长度表while(*str!='(')//偏移到参数开始的地方{str++;											    if(*str=='\0')return USMART_FUNCERR;//遇到结束符了}str++;//偏移到"("之后的第一个字节while(1){i=usmart_get_aparm(str,tstr,&type);	//得到第一个参数  str+=i;								//偏移switch(type){case 0:	//数字if(tstr[0]!='\0')				//接收到的参数有效{					    i=usmart_str2num(tstr,&res);	//记录该参数	 if(i)return USMART_PARMERR;		//参数错误.*(u32*)(usmart_dev.parm+usmart_get_parmpos(n))=res;//记录转换成功的结果.usmart_dev.parmtype&=~(1<<n);	//标记数字usmart_dev.plentbl[n]=4;		//该参数的长度为4  n++;							//参数增加  if(n>MAX_PARM)return USMART_PARMOVER;//参数太多}break;case 1://字符串	 	len=usmart_strlen(tstr)+1;	//包含了结束符'\0'usmart_strcopy(tstr,&usmart_dev.parm[usmart_get_parmpos(n)]);//拷贝tstr数据到usmart_dev.parm[n]usmart_dev.parmtype|=1<<n;	//标记字符串 usmart_dev.plentbl[n]=len;	//该参数的长度为len  n++;if(n>MAX_PARM)return USMART_PARMOVER;//参数太多break;case 0XFF://错误return USMART_PARMERR;//参数错误	  }if(*str==')'||*str=='\0')break;//查到结束标志了.}*parn=n;	//记录参数的个数return USMART_OK;//正确得到了参数
}

六.正点原子库–外设库–DHT11

1.dht11.h

#ifndef __DS18B20_H
#define __DS18B20_H
#include "sys.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//DHT11驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv
//创建日期:2017/4/14
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved
////IO方向设置
#define DHT11_IO_IN()  {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=0<<(9*2);}	//PG9输入模式
#define DHT11_IO_OUT() {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=1<<(9*2);} 	//PG9输出模式IO操作函数
#define	DHT11_DQ_OUT    PGout(9)//数据端口	PG9
#define	DHT11_DQ_IN     PGin(9) //数据端口	PG9u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif

2.dht11.c

#include "dht11.h"
#include "delay.h"
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32F407开发板
//DHT11驱动代码
//正点原子@ALIENTEK
//技术论坛:www.openedv
//创建日期:2017/4/15
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved
////复位DHT11
void DHT11_Rst(void)
{DHT11_IO_OUT(); 	//设置为输出DHT11_DQ_OUT=0; 	//拉低DQdelay_ms(20);    	//拉低至少18msDHT11_DQ_OUT=1; 	//DQ=1delay_us(30);     	//主机拉高20~40us
}//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{u8 retry=0;DHT11_IO_IN();      //设置为输出while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us{retry++;delay_us(1);};if(retry>=100)return 1;else retry=0;while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us{retry++;delay_us(1);};if(retry>=100)return 1;return 0;
}//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{u8 retry=0;while(DHT11_DQ_IN&&retry<100)//等待变为低电平{retry++;delay_us(1);}retry=0;while(!DHT11_DQ_IN&&retry<100)//等待变高电平{retry++;delay_us(1);}delay_us(40);//等待40usif(DHT11_DQ_IN)return 1;else return 0;
}//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{u8 i,dat;dat=0;for (i=0;i<8;i++){dat<<=1;dat|=DHT11_Read_Bit();}return dat;
}//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{u8 buf[5];u8 i;DHT11_Rst();if(DHT11_Check()==0){for(i=0;i<5;i++)//读取40位数据{buf[i]=DHT11_Read_Byte();}if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]){*humi=buf[0];*temp=buf[2];}}else return 1;return 0;
}//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{GPIO_InitTypeDef GPIO_Initure;__HAL_RCC_GPIOG_CLK_ENABLE();			//开启GPIOG时钟GPIO_Initure.Pin=GPIO_PIN_9;           	//PG9GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出GPIO_Initure.Pull=GPIO_PULLUP;          //上拉GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速HAL_GPIO_Init(GPIOG,&GPIO_Initure);     //初始化DHT11_Rst();return DHT11_Check();
}

七,自用HAL_Delay库

该库包含us和ms的Delay函数,实测可用

1.HAL_Delay.h

#include "stm32f4xx.h"
/*** @brief  HAL_Delay初始化*/
void HAL_Delay_Init(void);
/*** @brief  微秒延时* @param  us 延时微秒数;*/
void HAL_Delay_us(uint32_t us);
/*** @brief  毫秒延时* @param  ms 延时毫秒数;*/
void HAL_Delay_ms(uint16_t ms);

2.HAL_Delay.c

#include "HAL_delay.h"
uint32_t SysClk = 0;
uint32_t HClk = 0;
uint32_t PClk1 = 0;
uint32_t PClk2 = 0;
uint16_t CPU_FREQUENCY_MHZ=0;// STM32时钟主频
void HAL_Delay_Init(void)
{SysClk = HAL_RCC_GetSysClockFreq();HClk = HAL_RCC_GetHCLKFreq();PClk1 = HAL_RCC_GetPCLK1Freq();PClk2 = HAL_RCC_GetPCLK2Freq();CPU_FREQUENCY_MHZ=HClk/1000000;
}void HAL_Delay_us(uint32_t us)
{int last, curr, val;int temp;while (us != 0) {temp = us > 900 ? 900 : us;last = SysTick->VAL;curr = last - CPU_FREQUENCY_MHZ * temp;if (curr >= 0)                       {do {val = SysTick->VAL;} while ((val < last) && (val >= curr));} else {curr += CPU_FREQUENCY_MHZ * 1000;do {val = SysTick->VAL;} while ((val <= last) || (val > curr));}us -= temp;}
}void HAL_Delay_ms(uint16_t ms)
{HAL_Delay(ms);
}

八. 自用DHT22库

1.DHT22.h

#ifndef INC_DHT22_H_
#define INC_DHT22_H_
#include "stm32f4xx_hal.h"
#include "HAL_Delay.h"//#define DHT_PIN GPIO_PIN_11		//重命名引脚,可用可不用
//#define DHT_PORT GPIOA			//重命名IO名,同上/*** @brief  引脚初始化(命名)* @param  Port 即GPIOx;* @param  Pin 即GPIO_PIN_XX*/
void DHT22_Init(GPIO_TypeDef* Port, uint16_t Pin);/*** @brief  开始启动DHT22*/
void ONE_WIRE_Start(void);/*** @brief  DTH22的响应,若不正常则返回error*/
void ONE_WIRE_Response(void);/*** @brief  把数据线IO口设置为输出*/
void Gpio_Output(void);/*** @brief  把数据线IO口设置为输入*/
void Gpio_Input(void);/*** @brief  读取数据线的数据* @retval Uint8_t*/
uint8_t ONE_WIRE_Read_Data(void);/*** @brief  接收数据并对数据校验* @param  &Temp 温度* @param  &Hum 湿度*/
uint8_t DHT22_Get_Data(uint16_t *Temp,uint16_t *Hum);
#endif

2.DHT22.c

#include "DHT22.h"GPIO_TypeDef* wire_Port;
uint16_t wire_Pin;
uint8_t error;
uint8_t Hum_byte1;
uint8_t Hum_byte2;
uint8_t Temp_byte1;
uint8_t Temp_byte2;
uint8_t Sum;//例如GPIOA GPIO_PIN_11
void DHT22_Init(GPIO_TypeDef* Port,uint16_t Pin)
{wire_Port = Port;wire_Pin = Pin;
}//设置为输出,去拉高和拉低DHT22的电平
void Gpio_Output(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = wire_Pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(wire_Port, &GPIO_InitStruct);
}//设置为输入,以致于可以读到DHT22响应后采集的数据
void Gpio_Input(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};GPIO_InitStruct.Pin = wire_Pin;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(wire_Port, &GPIO_InitStruct);
}//让DHT22开始响应
void ONE_WIRE_START(void)
{Gpio_Output();HAL_GPIO_WritePin(wire_Port, wire_Pin, GPIO_PIN_RESET);//拉低HAL_Delay(1);//拉低1msHAL_GPIO_WritePin(wire_Port, wire_Pin, GPIO_PIN_SET);HAL_Delay_us(30);//拉高30us 具体时序见图2Gpio_Input();
}void ONE_WIRE_Responsee(void)
{HAL_Delay_us(40);//判断是否输出低电平if(!(HAL_GPIO_ReadPin(wire_Port, wire_Pin))){HAL_Delay_us(80);//判断是否输出高电平if(HAL_GPIO_ReadPin(wire_Port, wire_Pin)){//用来判断是否正常读出了数据error=1;}//等待高电平结束后退出循环while(HAL_GPIO_ReadPin(wire_Port, wire_Pin));}
}uint8_t ONE_WIRE_Read_Data(void)
{uint8_t i,j;for(j = 0;j < 8; j++){//等待低电平结束,低电平代表1bit开始while(!(HAL_GPIO_ReadPin(wire_Port, wire_Pin)));//延时50us后若是低电平则代表数据是0,反之为1HAL_Delay_us(50);if(HAL_GPIO_ReadPin(wire_Port, wire_Pin) == 0){//读出0i &= ~(1 << (7 - j));}else{ //读出 1i |= ( 1 << (7 - j));}//变为低电平时退出循环,开始下一bit的数据接收while(HAL_GPIO_ReadPin(wire_Port, wire_Pin));}return i;
}uint8_t DHT22_Get_Data(uint16_t *Temp,uint16_t *Hum)
{ONE_WIRE_START();ONE_WIRE_Responsee();//32位,先读湿度的高低位,再读温度的高低位Hum_byte1 = ONE_WIRE_Read_Data();Hum_byte2 = ONE_WIRE_Read_Data();Temp_byte1 = ONE_WIRE_Read_Data();Temp_byte2 = ONE_WIRE_Read_Data();Sum = ONE_WIRE_Read_Data();*Temp = (Temp_byte1 << 8) | Temp_byte2;*Hum = (Hum_byte1 << 8) | Hum_byte2;if(error == 1){return 1;} else{return 0;}
}

更多推荐

STM32 CLion+HAL+MX+OPENOCD FOR ARM 常用库分享(一)

本文发布于:2024-02-25 09:17:38,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1698493.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:常用   HAL   CLion   ARM   OPENOCD

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!