网站首页 > 博客文章 正文
1)实验平台:正点原子stm32mini 开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子
第五章 SYSTEM 文件夹介绍
第三章,我们介绍了如何在上一章,我们介绍了如何在 MDK5.23 下建立 STM32F1 工程,
在这个新建的工程之中,我们用到了一个 SYSTEM 文件夹里面的代码,此文件夹里面的代码由
ALIENTEK 提供,是 STM32F10x 系列的底层核心驱动函数,可以用在 STM32F10x 系列的各个
型号上面,方便大家快速构建自己的工程。
SYSTEM 文件夹下包含了 delay、sys、usart 等三个文件夹。分别包含了 delay.c、sys.c、usart.c
及其头文件。通过这 3 个 c 文件,可以快速的给任何一款 STM32F1 构建最基本的框架。使用
起来是很方便的。
本章,我们将向大家介绍这些代码,通过这章的学习,大家将了解到这些代码的由来,也
希望大家可以灵活使用 SYSTEM 文件夹提供的函数,来快速构建工程,并实际应用到自己的项
目中去。
本章包括如下 3 个小结:
5.1,delay 文件夹代码介绍;
5.2,sys 文件夹代码介绍;
5.3,usart 文件夹代码介绍;
5.1 delay 文件夹代码介绍
delay 文件夹内包含了 delay.c 和 delay.h 两个文件,这两个文件用来实现系统的延时功能,
其中包含 7 个函数:
void delay_osschedlock(void);
void delay_osschedunlock(void);
void delay_ostimedly(u32 ticks);
void SysTick_Handler(void);
void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
前面 4 个函数,仅在支持操作系统(OS)的时候,需要用到,而后面三个函数,则不论是
否支持 OS 都需要用到。
在介绍这些函数之前,我们先了解一下 delay 延时的编程思想:CM3 内核的处理器,内部
包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计数到 0 时,将从
RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。只要不把它在 SysTick 控制及状
态寄存器中的使能位清除,就永不停息。SysTick 在《STM32 中文参考手册》(这里是指 V10.0
版本,下同)里面介绍的很简单,其详细介绍,请参阅《Cortex-M3 权威指南》第 133 页。我
们就是利用 STM32 的内部 SysTick 来实现延时的,这样既不占用中断,也不占用系统定时器。
这里我们将介绍的是 ALIENTEK 提供的最新版本的延时函数,该版本的延时函数支持在任
意操作系统(OS)下面使用,它可以和操作系统共用 SysTick 定时器。
这里,我们以 UCOSII 为例,介绍如何实现操作系统和我们的 delay 函数共用 SysTick 定时
器。首先,我们简单介绍下 UCOSII 的时钟:ucos 运行需要一个系统时钟节拍(类似 “心跳”),
而这个节拍是固定的(由 OS_TICKS_PER_SEC 宏定义设置),比如要求 5ms 一次(即可设置:
OS_TICKS_PER_SEC=200),在 STM32 上面,一般是由 SysTick 来提供这个节拍,也就是 SysTick
要设置为 5ms 中断一次,为 ucos 提供时钟节拍,而且这个时钟一般是不能被打断的(否则就不
准了)因为在 ucos 下 SysTick 不能再被随意更改,如果我们还想利用 SysTick 来做 delay_us 或者
delay_ms 的延时,就必须想点办法了,这里我们利用的是时钟摘取法。以 delay_us 为例,比如
delay_us(50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 SysTick 计数次数,这里
为 50*9(假设系统时钟为 72Mhz,那么 SysTick 每增加 1,就是 1/72us),然后我们就一直统计
SysTick 的计数变化,直到这个值变化了 50*9,一旦检测到变化达到或者超过这个值,就说明
延时 50us 时间到了。这样,我们只是抓取 SysTick 计数器的变化,并不需要修改 SysTick 的任
何状态,完全不影响 SysTick 作为 UCOS 时钟节拍的功能,这就是实现 delay 和操作系统共用
SysTick 定时器的原理。
下面我们开始介绍这几个函数。
5.1.1 操作系统支持宏定义及相关函数
当需要 delay_ms 和 delay_us 支持操作系统(OS)的时候,我们需要用到 3 个宏定义和 4
个函数,宏定义及函数代码如下:
//本例程仅作 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 //使用 UCOSIII
OS_ERR err;
OSSchedLock(&err);
//UCOSIII 的方式,禁止调度,防止打断 us 延时
#else
//否则 UCOSII
OSSchedLock();
//UCOSII 的方式,禁止调度,防止打断 us 延时
#endif
}
//us 级延时时,恢复任务调度
void delay_osschedunlock(void)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII
OS_ERR err;
OSSchedUnlock(&err);
//UCOSIII 的方式,恢复调度
#else
//否则 UCOSII
OSSchedUnlock();
//UCOSII 的方式,恢复调度
#endif
}
//调用 OS 自带的延时函数延时
//ticks:延时的节拍数
void delay_ostimedly(u32 ticks)
{
#ifdef CPU_CFG_CRITICAL_METHOD //使用 UCOSIII 时
OS_ERR err;
OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);//UCOSIII 延时采用周期模式
#else
OSTimeDly(ticks);
//UCOSII 延时
#endif
}
//systick 中断服务函数,使用 ucos 时用到
void SysTick_Handler(void)
{
if(delay_osrunning==1)
//OS 开始跑了,才执行正常的调度处理
{
OSIntEnter();
//进入中断
OSTimeTick();
//调用 ucos 的时钟服务程序
OSIntExit();
//触发任务切换软中断
}
}
以上代码,仅支持 UCOSII 和 UCOSIII,不过,对于其他 OS 的支持,也只需要对以上代
码进行简单修改即可实现。
支持 OS 需要用到的三个宏定义(以 UCOSII 为例)即:
#define delay_osrunning
OSRunning
//OS 是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OS_TICKS_PER_SEC //OS 时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNesting
//中断嵌套级别,即中断嵌套次数
宏定义:delay_osrunning,用于标记 OS 是否正在运行,当 OS 已经开始运行时,该宏定义
值为 1,当 OS 还未运行时,该宏定义值为 0。
宏定义:delay_ ostickspersec,用于表示 OS 的时钟节拍,即 OS 每秒钟任务调度次数。
宏定义:delay_ osintnesting,用于表示 OS 中断嵌套级别,即中断嵌套次数,每进入一个
中断,该值加 1,每退出一个中断,该值减 1。
支持 OS 需要用到的 4 个函数,即:
函数:delay_osschedlock,用于 delay_us 延时,作用是禁止 OS 进行调度,以防打断 us 级
延时,导致延时时间不准。
函数:delay_osschedunlock,同样用于 delay_us 延时,作用是在延时结束后恢复 OS 的调度,
继续正常的 OS 任务调度。
函数:delay_ostimedly,则是调用 OS 自带的延时函数,实现延时。该函数的参数为时钟节
拍数。
函数:SysTick_Handler,则是 systick 的中断服务函数,该函数为 OS 提供时钟节拍,同时
可以引起任务调度。
以上就是 delay_ms 和 delay_us 支持操作系统时,需要实现的 3 个宏定义和 4 个函数。
5.1.2 delay_init 函数
该函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外
部时钟,如果需要支持操作系统(OS),只需要在 sys.h 里面,设置 SYSTEM_SUPPORT_OS 宏
的值为 1 即可,然后,该函数会根据 delay_ostickspersec 宏的设置,来配置 SysTick 的中断时间,
并开启 SysTick 中断。具体代码如下:
//初始化延迟函数
//当使用 OS 的时候,此函数会初始化 OS 的时钟节拍
//SYSTICK 的时钟固定为 HCLK 时钟
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS
//如果需要支持 OS.
u32 reload;
#endif
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
//SysTick 频率为 HCLK
fac_us=SYSCLK;
//不论是否使用 OS,fac_us 都需要使用
#if SYSTEM_SUPPORT_OS
//如果需要支持 OS.
reload=SYSCLK;
//每秒钟的计数次数 单位为 K
reload*=1000000/delay_ostickspersec; //根据 delay_ostickspersec 设定溢出时间
//reload 为 24 位寄存器,最大值:16777216,在 72M 下,约合 0.233s 左右
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
}
可以看到,delay_init 函数使用了条件编译,来选择不同的初始化过程,如果不使用 OS 的
时候,只是设置一下 SysTick 的时钟源以及确定 fac_us 和 fac_ms 的值。而如果使用 OS 的时候,
则会进行一些不同的配置,这里的条件编译是根据 SYSTEM_SUPPORT_OS 这个宏来确定的,
该宏在 sys.h 里面定义。
SysTick 是 MDK 定义了的一个结构体(在 core_m3.h 里面),里面包含 CTRL、LOAD、VAL、
CALIB 等 4 个寄存器,
SysTick->CTRL 的各位定义如图 5.1.2.1 所示:
SysTick-> LOAD 的定义如图 5.1.2.2 所示:
SysTick-> VAL 的定义如图 5.1.2.3 所示:
SysTick-> CALIB 不常用,在这里我们也用不到,故不介绍了。
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);这一句把 SysTick 的
时钟选择外部时钟,这里需要注意的是:SysTick 的时钟源自 HCLK,假设我们外部晶振为 8M,
然后倍频到 72M,那么 SysTick 的时钟即为 72Mhz,也就是 SysTick 的计数器 VAL 每减 1,就
代表时间过了 1/72us。所以 fac_us=SYSCLK;这句话就是计算在 SYSCLK 时钟频率下延时 1us
需要多少个 SysTick 时钟周期。
在不使用 OS 的时候:fac_us,为 us 延时的基数,也就是延时 1us,Systick 定时器需要走
过的时钟周期数。 当使用 OS 的时候,fac_us,还是 us 延时的基数,不过这个值不会被写到
SysTick->LOAD 寄存器来实现延时,而是通过时钟摘取的办法实现的(前面已经介绍了)。而
fac_ms 则代表 ucos 自带的延时函数所能实现的最小延时时间(如 delay_ostickspersec=200,那
么 fac_ms 就是 5ms)。
5.1.3 delay_us 函数
该函数用来延时指定的 us,其参数 nus 为要延时的微秒数。该函数有使用 OS 和不使用 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;
//需要的节拍数
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;
//时间超过/等于要延迟的时间,则退出.
}
};
}
再来看看使用 OS 的时候,delay_us 的实现函数如下:
//延时 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 调度
}
这里就正是利用了我们前面提到的时钟摘取法,ticks 是延时 nus 需要等待的 SysTick 计数
次数(也就是延时时间),told 用于记录最近一次的 SysTick->VAL 值,然后 tnow 则是当前的
SysTick->VAL 值,通过他们的对比累加,实现 SysTick 计数次数的统计,统计值存放在 tcnt 里
面,然后通过对比 tcnt 和 ticks,来判断延时是否到达,从而达到不修改 SysTick 实现 nus 的延
时。
对于使用 OS 的时候,delay_us 的实现函数和不使用 OS 的时候方法类似,都是使用的时钟
摘取法,只不过使用 delay_osschedlock 和 delay_osschedunlock 两个函数,用于调度上锁和解锁,
这是为了防止 OS 在 delay_us 的时候打断延时,可能导致的延时不准,所以我们利用这两个函
数来实现免打断,从而保证延时精度。
5.1.4 delay_ms 函数
该函数用来延时指定的 ms,其参数 nms 为要延时的毫秒数。该函数同样有使用 OS 和不使
用 OS 两个版本,这里我们分别介绍,首先是不使用 OS 的时候,实现函数如下:
//延时 nms
//nms:要延时的 ms 数
void delay_ms(u16 nms)
{
u32 i;
for(i=0;i<nms;i++) delay_us(1000);
}
该函数其实就是多次调用前面所讲的 delay_us 函数,来实现毫秒级延时的。
再来看看使用 OS 的时候,delay_ms 的实现函数如下:
//延时 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)); //普通方式延时
}
该函数中,delay_osrunning 是 OS 正在运行的标志,delay_osintnesting 则是 OS 中断嵌套次
数,必须 delay_osrunning 为真,且 delay_osintnesting 为 0 的时候,才可以调用 OS 自带的延时
函数进行延时(可以进行任务调度),delay_ostimedly 函数就是利用 OS 自带的延时函数,实现
任 务级 延时 的, 其参数 代表 延时 的时 钟节拍 数( 假设 delay_ostickspersec=200 ,那 么
delay_ostimedly (1),就代表延时 5ms)。
当 OS 还未运行的时候,我们的 delay_ms 就是直接由 delay_us 实现的,OS 下的 delay_us
可以实现很长的延时(达到 204 秒)而不溢出!,所以放心的使用 delay_us 来实现 delay_ms,
不过由于 delay_us 的时候,任务调度被上锁了,所以还是建议不要用 delay_us 来延时很长的时
间,否则影响整个系统的性能。
当 OS 运行的时候,我们的 delay_ms 函数将先判断延时时长是否大于等于 1 个 OS 时钟节
拍(fac_ms),当大于这个值的时候,我们就通过调用 OS 的延时函数来实现(此时任务可以调
度),不足 1 个时钟节拍的时候,直接调用 delay_us 函数实现(此时任务无法调度)。
5.1.5 HAL 库延时函数 HAL_Delay 解析
前面我们讲解了 ALIENTEK 提供的使用 Systick 实现延时相关函数。实际上,HAL 库有提
供延时函数,只不过它只能实现简单的毫秒级别延时,没有实现 us 级别延时。下面我们列出
HAL 库实现延时相关的函数。首先是功能配置函数:
//调用 HAL_SYSTICK_Config 函数配置每隔 1ms 中断一次:文件 stm32f1xx_hal.c 中定义
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* 配置系统在 1ms 的时间基础上有中断*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* 配置 SysTick IRQ 优先级*/
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
return HAL_OK;
}
//HAL 库的 SYSTICK 配置函数:文件 stm32f1xx_hal_context.c 中定义
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
return SysTick_Config(TicksNumb);
}
//内核的 Systick 配置函数,配置每隔 ticks 个 systick 周期中断一次
//文件 core_cm3.h 中
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
...//此处省略函数定义
}
上面三个函数,实际上开放给 HAL 调用的主要是 HAL_InitTick 函数,该函数在 HAL 库初
始化函数 HAL_Init 中会被调用。该函数通过间接调用 SysTick_Config 函数配置 Systick 定时器
每隔 1ms 中断一次,永不停歇。
接下来我们来看看延时的逻辑控制代码:
//Systick 中断服务函数:文件 stm32f4xx_it.c 中
void SysTick_Handler(void)
{
HAL_IncTick();
}
//下面代码均在文件 stm32f1xx_hal.c 中
static __IO uint32_t uwTick; //定义计数全局变量
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
__weak uint32_t HAL_GetTick(void) //获取全局变量 uwTick 的值
{
return uwTick;
}
//开放的 HAL 延时函数,延时 Delay 毫秒
__weak void HAL_Delay(__IO uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while ((HAL_GetTick() - tickstart) < wait)
{
}
}
HAL 库实现延时功能非常简单,首先定义了一个 32 位全局变量 uwTick,在 Systick 中断
服务函数 SysTick_Handler 中通过调用 HAL_IncTick 实现 uwTick 值不断增加,也就是每隔 1ms
增加 1。而 HAL_Delay 函数在进入函数之后先记录当前 uwTick 的值,然后不断在循环中读取
uwTick 当前值,进行减运算,得出的就是延时的毫秒数,整个逻辑非常简单也非常清晰。
但是,HAL 库的延时函数有一个局限性,在中断服务函数中使用 HAL_Delay会引起混乱,
因为它是通过中断方式实现,而 Systick 的中断优先级是最低的,所以在中断中运行 HAL_Delay
会导致延时出现严重误差。所以一般情况下,推荐大家使用 ALIENTEK 提供的延时函数库。
5.2 sys 文件夹代码介绍
sys 文件夹内包含了 sys.c 和 sys.h 两个文件。在 sys.h 里面定义了 STM32F1 的 IO 口位
操作输入读取宏定义和输出宏定义以及类型别名。sys.c 里面除了定义时钟系统配置函数
Stm32_Clock_Init 外主要是一些汇编函数,对于函数 Stm32_Clock_Init 的讲解请参考本手
册 4.3 小节 STM32F103 时钟系统章节内容。本小节我们主要向大家介绍 sys.h 头文件里面
的 IO 口位操作。
5.2.1 IO 口的位操作实现
该部分代码在 sys.h 文件中,实现对 STM32F1 各个 IO 口的位操作,包括读入和输出。
当然在这些函数调用之前,必须先进行 IO 口时钟的使能和 IO 口功能定义。此部分仅仅对
IO 口进行输入输出读取和控制。
位带操作简单的说,就是把每个比特膨胀为一个 32 位的字,当访问这些字的时候就达
到了访问比特的目的,比如说 GPIO 的 ODR 寄存器有 32 个位,那么可以映射到 32 个地址
上,我们去访问这 32 个地址就达到访问 32 个比特的目的。这样我们往某个地址写 1 就达
到往对应比特位写 1 的目的,同样往某个地址写 0 就达到往对应的比特位写 0 的目的。
对于上图,我们往 Address0 地址写入 1,那么就可以达到往寄存器的第 0 位 Bit0 赋值
1 的目的。这里我们不想讲得过于复杂,因为位带操作在实际开发中可能只是用来 IO 口的
输入输出还比较方便,其他操作在日常开发中也基本很少用。下面我们看看 sys.h 中位带
操作的定义。
代码如下:
//位带操作,实现 51 类似的 GPIO 控制功能
//具体实现思想,参考<<CM3 权威指南>>第五章(87 页~92 页).
//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+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
……//省略部分代码
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
……//省略部分代码
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//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 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) //输入
以上代码的便是 GPIO 位带操作的具体实现,位带操作的详细说明,在权威指南中有
详细讲解,请参考<<CM3 权威指南>>第五章(87 页~92 页)。比如说,我们调用 PAout(1)=1
是设置了 GPIOA 的第一个管脚 GPIOA.1 为 1,实际是设置了寄存器的某个位,但是我们
的定义中可以跟踪过去看到却是通过计算访问了一个地址。上面一系列公式也就是计算
GPIO 的某个 io 口对应的位带区的地址了。
有了上面的代码,我们就可以像51/AVR一样操作STM32的IO口了。比如,我要PORTA
的第七个 IO 口输出 1,则可以使用 PAout(6)=1;即可实现。我要判断 PORTA 的第 15
个位是否等于 1,则可以使用 if(PAin(14)==1)…;就可以了。
这里顺便说一下,在 sys.h 中的还有个全局宏定义:
//0,不支持 ucos
//1,支持 ucos
#define SYSTEM_SUPPORT_OS
0
//定义系统文件夹是否支持 UCOS
SYSTEM_SUPPORT_OS,这个宏定义用来定义 SYSTEM 文件夹是否支持 ucos,如果
在 ucos 下面使用 SYSTEM 文件夹,那么设置这个值为 1 即可,否则设置为 0(默认)。
5.3 usart 文件夹介绍
该文件夹下面有 usart.c 和 usarts.h 两个文件。串口相关知识,我们将在第九章讲解串
口实验的时候给大家详细讲解。本节我们只给大家讲解比较独立的 printf 函数支持相关的
知识。
5.3.1 printf 函数支持
printf 函数支持的代码在 usart.c 文件的最上方,在我们初始化和使能串口 1 之后,然
后把这段代码加入到工程,便可以通过 printf 函数向串口 1 发送我们需要的内容,方便开
发过程中查看代码执行情况以及一些变量值。这段代码如果要修改一般也只是用来改变
printf 函数针对的串口号,大多情况我们都不需要修改。
代码内容如下:
//加入以下代码,支持 printf 函数,而不需要选择 use MicroLIB
#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
猜你喜欢
- 2024-09-09 国产鼠标新标杆 钛度智能游戏鼠标拆解
- 2024-09-09 STM32学习,从点灯开始(stm32f103点灯程序教程)
- 2024-09-09 学嵌入式STM32系列芯片应该怎么选择,都有什么区别
- 2024-09-09 STM32单片机详细教学(二):STM32系列单片机的介绍
- 2024-09-09 STM32F103资料下载和介绍(stm32f103zet6资料)
- 2024-09-09 「正点原子Linux连载」第十七章GPIO中断试验(一)
- 2024-09-09 基于STM32的温度控制系统(含代码)
- 2024-09-09 STM32F103 串口的使用方法(stm32f103串口1)
- 2024-09-09 你知道怎么用STM32控制舵机吗?(stm32f407控制舵机)
- 2024-09-09 (STM32F103)神操作——如何快速读写内部flash?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)