SYSTEM 文件夹介绍

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(void);
void delay_ms(u16 nms);
void delay_us(u32 nus);

前面 4 个函数,仅在支持操作系统(OS)的时候,需要用到,而后面三个函数,则不论是
否支持 OS 都需要用到。
1.1 操作系统支持宏定义及相关函数
当需要 delay_ms 和 delay_us 支持操作系统(OS)的时候,我们需要用到 3 个宏定义和 4 个
函数,宏定义及函数代码如下:

#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 个函数。
1.2delay_init 函数
该函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外部时
钟,如果需要支持操作系统(OS),只需要在 sys.h 里面,设置 SYSTEM_SUPPORT_OS 宏的值
为 1 即可,然后,该函数会根据 delay_ostickspersec 宏的设置,来配置 SysTick 的中断时间,并
开启 SysTick 中断。具体代码如下:

//初始化延迟函数
//当使用 OS 的时候,此函数会初始化 OS 的时钟节拍
//SYSTICK 的时钟固定为 HCLK 时钟的 1/8
void delay_init()
{
#if SYSTEM_SUPPORT_OS //如果需要支持 OS.
u32 reload;
#endif
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8000000; //为系统时钟的 1/8 
#if SYSTEM_SUPPORT_OS //如果需要支持 OS.
reload=SystemCoreClock/8000000; //每秒钟的计数次数 单位为 M 
reload*=1000000/delay_ostickspersec; //根据 delay_ostickspersec 设定溢出时间
//reload 为 24 位寄存器,最大值:16777216,在 72M 下,约合 1.86s 左右
fac_ms=1000/delay_ostickspersec; //代表 OS 可以延时的最少单位 
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启 SYSTICK 中断
SysTick->LOAD=reload; //每 1/delay_ostickspersec 秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启 SYSTICK 
#else
fac_ms=(u16)fac_us*1000; //非 OS 下,代表每个 ms 需要的 systick 时钟数 
#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_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);这一句把SysTick的时钟选择
外部时钟,这里需要注意的是:SysTick 的时钟源自 HCLK 的 8 分频,假设我们外部晶振为 8M,
然后倍频到 72M,那么 SysTick 的时钟即为 9Mhz,也就是 SysTick 的计数器 VAL 每减 1,就代
表时间过了 1/9us。所以 fac_us=SystemCoreClock/8000000;这句话就是计算在 SystemCoreClock
时钟频率下延时 1us 需要多少个 SysTick 时钟周期。同理,fac_ms=(u16)fac_us*1000;就是计算
延时 1ms 需要多少个 SysTick 时钟周期,它自然是 1us 的 1000 倍。初始化将计算出 fac_us 和
fac_ms 的值。
在不使用 OS 的时候:fac_us,为 us 延时的基数,也就是延时 1us,SysTick->LOAD 所应设
置的值。fac_ms 为 ms 延时的基数,也就是延时 1ms,SysTick->LOAD 所应设置的值。fac_us 为
8 位整形数据,fac_ms 为 16 位整形数据。Systick 的时钟来自系统时钟 8 分频,正因为如此,系
统时钟如果不是 8 的倍数(不能被 8 整除),则会导致延时函数不准确,这也是我们推荐外部时
钟选择 8M 的原因。这点大家要特别留意。
当使用 OS 的时候,fac_us,还是 us 延时的基数,不过这个值不会被写到 SysTick->LOAD
寄存器来实现延时,而是通过时钟摘取的办法实现的(前面已经介绍了)。而 fac_ms 则代表 ucos
自带的延时函数所能实现的最小延时时间(如 delay_ostickspersec=200,那么 fac_ms 就是 5ms)。
1.3 delay_us 函数
该函数用来延时指定的 us,其参数 nus 为要延时的微秒数。该函数有使用 OS 和不使用 OS
两个版本,这里我们分别介绍,首先是不使用 OS 的时候,实现函数如下:

//延时 nus
//nus 为要延时的 us 数. 
void delay_us(u32 nus)
{
u32 temp; 
SysTick->LOAD=nus*fac_us; //时间加载 
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16)));//等待时间到达 
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}

有了上面对 SysTick 寄存器的描述,这段代码不难理解。其实就是先把要延时的 us 数换算
成 SysTick 的时钟数,然后写入 LOAD 寄存器。然后清空当前寄存器 VAL 的内容,再开启倒数
功能。等到倒数结束,即延时了 nus。最后关闭 SysTick,清空 VAL 的值。实现一次延时 nus 的
操作,但是这里要注意 nus 的值,不能太大,必须保证 nus<=(2^24)/fac_us,否则将导致延时
时间不准确。这里特别说明一下:temp&0x01,这一句是用来判断 systick 定时器是否还处于开
启状态,可以防止 systick 被意外关闭导致的死循环。
再来看看使用 OS 的时候,delay_us 的实现函数如下:

//延时 nus
//nus 为要延时的 us 数. 
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 共用一个 SysTick。
上面的 delay_osschedlock 和 delay_osschedunlock 是 OS 提供的两个函数,用于调度上锁和
解锁,这里为了防止 OS 在 delay_us 的时候打断延时,可能导致的延时不准,所以我们利用这
两个函数来实现免打断,从而保证延时精度!同时,此时的 delay_us,可以实现最长 2^32us 的
延时,大概是 4294 秒。
1.4 delay_ms 函数
该函数用来延时指定的 ms,其参数 nms 为要延时的毫秒数。该函数同样有使用 OS 和不使
用 OS 两个版本,这里我们分别介绍,首先是不使用 OS 的时候,实现函数如下:

//延时 nms
//注意 nms 的范围
//SysTick->LOAD 为 24 位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK 单位为 Hz,nms 单位为 ms
//对 72M 条件下,nms<=1864 
void delay_ms(u16 nms)
{ 
u32 temp; 
SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD 为 24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16)));//等待时间到达 
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器 
}

最大延迟 ms 数可以通过公式:nms<=0xffffff81000/SYSCLK 计算。SYSCLK 单位
为 Hz,nms 的单位为 ms。如果时钟为 72M,那么 nms 的最大值为 1864ms。超过这个值,建议
通过多次调用 delay_ms 实现,否则就会导致延时不准确。
再来看看使用 OS 的时候,delay_ms 的实现函数如下:

//延时 nms
//nms:要延时的 ms 数
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
可以实现很长的延时而不溢出!,所以放心的使用 delay_us 来实现 delay_ms,不过由于 delay_us
的时候,任务调度被上锁了,所以还是建议不要用 delay_us 来延时很长的时间,否则影响整个
系统的性能。
当 OS 运行的时候,我们的 delay_ms 函数将先判断延时时长是否大于等于 1 个 OS 时钟节
拍(fac_ms),当大于这个值的时候,我们就通过调用 OS 的延时函数来实现(此时任务可以调
度),不足 1 个时钟节拍的时候,直接调用 delay_us 函数实现(此时任务无法调度)。
2 sys 文件夹代码介绍
sys 文件夹内包含了 sys.c 和 sys.h 两个文件。在 sys.h 里面定义了 STM32 的 IO 口输入读取
宏定义和输出宏定义。sys.c 里面只定义了一个中断分组函数。下面我们将分别向大家介绍。
2.1 IO 口的位操作实现
该部分代码在 sys.h 文件中,实现对 STM32 各个 IO 口的位操作,包括读入和输出。当然
在这些函数调用之前,必须先进行 IO 口时钟的使能和 IO 口功能定义。此部分仅仅对 IO 口进
行输入输出读取和控制。
位带操作简单的说,就是把每个比特膨胀为一个 32 位的字,当访问这些字的时候就达到了
访问比特的目的,比如说 BSRR 寄存器有 32 个位,那么可以映射到 32 个地址上,我们去访问
这 32 个地址就达到访问 32 个比特的目的。这样我们往某个地址写 1 就达到往对应比特位写 1
的目的,同样往某个地址写 0 就达到往对应的比特位写 0 的目的。
对于上图,我们往 Address0 地址写入 1,那么就可以达到往寄存器的第 0 位 Bit0 赋值 1 的
目的。这里我们不想讲得过于复杂,因为位带操作在实际开发中可能只是用来 IO 口的输入输出
还比较方便,其他操作在日常开发中也基本很少用。下面我们看看 sys.h 中位带操作的定义。
代码如下:

#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 GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C 
#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 GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808 
#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 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) //输入

以上代码的便是 GPIO 位带操作的具体实现,位带操作的详细说明,在权威指南中有详细
讲解,请参考<<CM3 权威指南>>第五章(87 页~92 页)。比如说,我们调用 PAout(1)=1 是设置了
GPIOA 的第一个管脚 GPIOA.1 为 1,实际是设置了寄存器 ODR 的某个位,但是我们的定义中
可以跟踪过去看到却是通过计算访问了一个地址。上面一系列公式也就是计算 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(默认)。
3 usart 文件夹介绍
usart 文件夹内包含了 usart.c 和 usart.h 两个文件。这两个文件用于串口的初始化和中断接
收。这里只是针对串口 1,比如你要用串口 2 或者其他的串口,只要对代码稍作修改就可以了。
usart.c 里面包含了 2 个函数一个是 void USART1_IRQHandler(void);另外一个是 void uart_init(u32
bound);里面还有一段对串口 printf 的支持代码,如果去掉,则会导致 printf 无法使用,虽然软件
编译不会报错,但是硬件上 STM32 是无法启动的,这段代码不要去修改。
3.1 printf 函数支持
这段引入 printf 函数支持的代码在 usart.h 头文件的最上方,这段代码加入之后便可以通过
printf 函数向串口发送我们需要的内容,方便开发过程中查看代码执行情况以及一些变量值。这
段代码不需要修改,引入到 usart.h 即可。
这段代码为:

//加入以下代码,支持 printf 函数,而不需要选择 use MicroLIB
#if 1
#pragma import(__use_no_semihosting) 
//标准库需要的支持函数 
struct __FILE 
{ 
int handle; 
}; 
STM32F103 最小系统板教程
124
STM32F1 开发指南(库函数版)
FILE __stdout; 
//定义_sys_exit()以避免使用半主机模式 
_sys_exit(int x) 
{ 
x = x; 
} 
//重定义 fputc 函数
int fputc(int ch, FILE *f)
{ 
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); 
 USART_SendData(USART1,(uint8_t)ch); 
return ch;
}
#endif

3.2 uart_init 函数
void uart_init(u32 pclk2,u32 bound)函数是串口 1 初始化函数。该函数有 1 个参数为波特
率,波特率这个参数对于大家来说应该不陌生,这里就不多说了。

void uart_init(u32 bound){
 //GPIO 端口设置
 GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA
, ENABLE); //使能 USART1,GPIOA 时钟
 //USART1_TX PA.9
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 复用推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0 发送端
 
 //USART1_RX PA.10 浮空输入
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10 接收端
 //Usart1 NVIC 中断配置 配置
 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //对应中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //中断优先级配置
 
 //USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl= 
USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx |USART_Mode_Tx;//收发模式
 USART_Init(USART1, &USART_InitStructure); //初始化串口
 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
 USART_Cmd(USART1, ENABLE); //使能串口
} 

下面我们一一分析一下这段初始化代码。首先是一行时钟使能代码:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|
RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA 时钟
所以接下来的两段代码就是将 TX(PA9)设置为推挽复用输出模式,将 RX(PA10)设置为浮空输入
模式:

 //USART1_TX PA.9
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 复用推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
 GPIO_Init(GPIOA, &GPIO_InitStructure);
 
 //USART1_RX PA.10 浮空输入
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
 GPIO_Init(GPIOA, &GPIO_InitStructure); 
紧接着,我们要进行 usart1 的中断初始化,设置抢占优先级值和子优先级的值:
 //Usart1 NVIC 中断配置 配置
 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器
这段代码在我们的中断管理函数章节 4.5 有讲解中断管理相关的知识,大家可以翻阅一下。
在设置完中断优先级之后,接下来我们要设置串口 1 的初始化参数:
 //USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//波特率设置;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl= 
USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发
USART_Init(USART1, &USART_InitStructure); //初始化串口

从上面的源码我们可以看出,串口的初始化是通过调用 USART_Init()函数实现,而这个函
数重要的参数就是就是结构体指针变量 USART_InitStructure,下面我们看看结构体定义:

typedef struct
{ 
uint32_t USART_BaudRate; 
 uint16_t USART_WordLength; 
 uint16_t USART_StopBits; 
 uint16_t USART_Parity; 
 uint16_t USART_Mode; 
 uint16_t USART_HardwareFlowControl; 
} USART_InitTypeDef;

这个结构体有 6 个成员变量,所以我们有 6 个参数需要初始化。
第一个参数 USART_BaudRate 为串口波特率,波特率可以说是串口最重要的参数了,我们
这里通过初始化传入参数 baund 来设定。第二个参数 USART_WordLength 为字长,这里我们设
置为 8 位字长数据格式。第三个参数 USART_StopBits 为停止位设置,我们设置为 1 位停止位。
第四个参数 USART_Parity 设定是否需要奇偶校验,我们设定为无奇偶校验位。第五个参数
USART_Mode 为串口模式,我们设置为全双工收发模式。第六个参数为是否支持硬件流控制,
我们设置为无硬件流控制。
在设置完成串口中断优先级以及串口初始化之后,接下来就是开启串口中断以及使能串口
了:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
USART_Cmd(USART1, ENABLE); //使能串口
在开启串口中断和使能串口之后接下来就是写中断处理函数了,下面一节我们将着重讲解
中断处理函数。
3.3 USART1_IRQHandler 函数
void USART1_IRQHandler(void)函数是串口 1 的中断响应函数,当串口 1 发生了相应的中
断后,就会跳到该函数执行。中断相应函数的名字是不能随便定义的,一般我们都遵循 MDK 定
义的函数名。这些函数名字在启动文件 startup_stm32f10x_hd.s 文件中可以找到。
函数体里面通过函数:
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
判断是否接受中断,如果是串口接受中断,则读取串口接受到的数据:
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
读到数据后接下来就对数据进行分析。
这里我们设计了一个小小的接收协议:通过这个函数,配合一个数组 USART_RX_BUF[],
一个接收状态寄存器 USART_RX_STA(此寄存器其实就是一个全局变量,由作者自行添加。由
于它起到类似寄存器的功能,这里暂且称之为寄存器)实现对串口数据的接收管理。

USART_RX_BUF 的大小由 USART_REC_LEN 定义,也就是一次接收的数据最大不能超过
USART_REC_LEN 个字节。中断相应函数代码如下:
void USART1_IRQHandler(void) //串口 1 中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS
OSIntEnter(); 
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 
//接收中断(接收到的数据必须是 0x0d 0x0a 结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了 0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到 0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收 
}
}
} 
 } 
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS.
OSIntExit(); 
#endif
}

EN_USART1_RX 和 USART_REC_LEN 都是在 usart.h 文件里面定义的,当需要使用串口
接收的时候,我们只要在 usart.h 里面设置 EN_USART1_RX 为 1 就可以了。不使用的时候,设
置,EN_USART1_RX 为 0 即可,这样可以省出部分 sram 和 flash,我们默认是设置
EN_USART1_RX 为 1,也就是开启串口接收的。
SYSTEM_SUPPORT_OS,则是用来判断是否使用 OS,如果使用了 OS,则调用 OSIntEnter
和 OSIntExit 函数,如果没有使用 ucos,则不调用这两个函数(这两个函数用于实现中断嵌套
处理,这里我们先不理会)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智商已欠费.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值