1
完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
本帖最后由 正点原子运营官 于 2020-4-16 14:49 编辑
1)实验平台:ALIENTEK NANO STM32F411 V1开发板 2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子 第五章 SYSTEM 文件夹介绍 第三章,我们介绍了如何在 MDK5 下建立 STM32F4 工程,在这个新建的工程之中,我们用到了一个 SYSTEM 文件夹里面的代码,此文件夹里面的代码由 ALIENTEK 提供,是STM32F4x 系列的底层核心驱动函数,可以用在 STM32F4x 系列的各个型号上面,方便大家快速构建自己的工程。 SYSTEM 文件夹下包含了 delay、sys、usart 等三个文件夹。分别包含了 delay.c、sys.c、usart.c及其头文件。通过这 3 个 c 文件,可以快速的给任何一款 STM32F4 构建最基本的框架。使用起来是很方便的。 本章,我们将向大家介绍这些代码,通过这章的学习,大家将了解到这些代码的由来,也希望大家可以灵活使用 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(void); 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(假设系统时钟为 96Mhz,那么 SysTick 每增加 1,就是 1/12us),然后我们就一直统计 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.2delay_init 函数 该函数用来初始化 2 个重要参数:fac_us 以及 fac_ms;同时把 SysTick 的时钟源选择为外部时钟,如果需要支持操作系统(OS),只需要在 sys.h 里面,设置 SYSTEM_SUPPORT_OS 宏的值为 1 即可,然后,该函数会根据 delay_ostickspersec 宏的设置,来配置 SysTick 的中断时间,并开启 SysTick 中断。具体代码如下: //初始化延迟函数 //当使用 ucos 的时候,此函数会初始化 ucos 的时钟节拍 //SYSTICK 的时钟固定为 AHB 时钟 //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_m4.h 里面),里面包含 CTRL、LOAD、 VAL、CALIB 等 4 个寄存器, SysTick->CTRL 的各位定义如图 5.1.2.1 所示: 图 5.1.2.1 SysTick->CTRL 寄存器各位定义 SysTick-> LOAD 的定义如图 5.1.2.2 所示: 图 5.1.2.2 SysTick->LOASysTick-> VAL 的定义如图 5.1.2.3 所示:D 寄存器各位定义 图 5.1.2.3 SysTick->VAL 寄存器各位定义 SysTick-> CALIB 不常用,在这里我们也用不到,故不介绍了。 HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);这句把 SysTick 的时钟选择外部时钟,这里需要注意的是:SysTick 的时钟源自 HCLK,假设我们外部晶振为 8M, 然后倍频到 96M,那么 SysTick 的时钟即为 96Mhz,也就是 SysTick 的计数器 VAL 每减 1,就代表时间过了 1/96us。所以 fac_us=SYSCLK;这句话就是计算在 SystemCoreClock 时钟频率下延 时 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 else tcnt+=reload-tnow+told; told=tnow; if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出. } }; } 这里就正是利用了我们前面提到的时钟摘取法, tick 是延时 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 该函数其实就是多次调用前面所讲的 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_osrunnig 是 OS 正在运行的标志,delay_osintnesting 则是 OS 中断嵌套次数,必须是 delay_osrunning 为真,且 delay_osintnesting 为 0 的时候,才可以调用 OS 自带的延时函数进行延时(可以进行任务调度),delay_ostimedly 函数就是利用 OS 自带的延时函数实现任务级延时的,其参数代表延时的时钟节拍数(假设 delay_ostickspersec=200,纳闷我们 delay_osrimedly(1),就代表延时 5ms)。 当 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 中断一次:文件 stm32f4xx_hal.c 中定义: __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) { HAL_SYSTICK_Config(SystemCoreClock/1000U); HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U); return HAL_OK; } HAL 库的 SYSTICK 配置函数,在文件 stm32f4xx_hal_context.c 中定义 uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb) { return SysTick_Config(TicksNumb); } 内核的 Systick 配置函数,配置每隔 ticks 个 systick 周期中断一次,在文件 core_cm4.h 中定 义: __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks) { ...//此处省略函数定义 } 上面三个函数,实际上开放给 HAL 调用的主要是 HAL_InitTick 函数,该函数在 HAL 库初始化函数 HAL_Init 中会被调用。该函数通过间接调用 SysTick_Config 函数配置 Systick 定时器每隔 1ms 中断一次,永不停歇。 接下来我们来看看延时的逻辑控制代码: Systick 中断服务函数:文件 stm32f1xx_it.c 中 void SysTick_Handler(void) { HAL_IncTick(); } 下面代码均在文件 stm32f1xx_hal.c 中: __IO uint32_t uwTick;//定义计数全局变量 __weak void HAL_IncTick(void)//全局变量 uwTick 递增 { uwTick++; } __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++; } while((HAL_GetTick() - tickstart) < wait) { } } HAL 库实现延时功能非常简单,首先定义了一个 32 位全局变量 uwTick,在 Systick 中断服务函数 SysTick_Handler 中通过调用 HAL_IncTick 实现 uwTick 值不断增加,也就是每隔 1ms增加 1。而 HAL_Delay 函数在进入函数之后先记录当前 uwTick 的值,确定定时时间的 wait 值, 然后不断在循环中读取 uwTick 当前值,进行减运算,得出的就是延时的毫秒数,整个逻辑非常简单也非常清晰。 但是,HAL 库的延时函数有一个局限性,在中断服务函数中使用 HAL_Delay 会引起混乱,因为它是通过中断方式实现,而 Systick 的中断优先级是最低的,所以在中断中运行 HAL_delay会导致延时出现严重误差。所以一般情况下,推荐大家使用 ALIENTEK 提供的延时函数库。 5.2 sys 文件夹代码介绍 sys 文件夹内包含了 sys.c 和 sys.h 两个文件。在 sys.h 里面定义了 STM32 的 IO 口位操作输入 读 取 宏 定 义 和 输 出 宏 定 义 以 及 类 型 别 名 。 sys.c 里 面 除 了 定 义 时 钟 系 统 配 置 函 数 Stm32_Clock_Init 外主要是一些汇编函数,对于函数 Stm32_Clock_Init 的讲解请参考本手册 4.3小节 STM32F1 时钟系统章节内容。本小节我们主要想大家介绍 sys.h 头文件里面的 IO 口位操 作。 5.2.1 IO 口的位操作实现 该部分代码在 sys.h 文件中,实现对 STM32F4 各个 IO 口的位操作,包括读入和输出。当 然在这些函数调用之前,必须先进行 IO 口时钟的使能和 IO 口功能定义。此部分仅仅对 IO 口进行输入输出读取和控制。 位带操作简单的说,就是把每个比特膨胀为一个 32 位的字,当访问这些字的时候就达到了访问比特的目的,比如说 GPIO 的 ODR 寄存器有 32 个位,那么可以映射到 32 个地址上,我们 去访问这 32 个地址就达到访问 32 个比特的目的。这样我们往某个地址写 1 就达到往对应比特位写 1 的目的,同样往某个地址写 0 就达到往对应的比特位写 0 的目的。 图 5.2.2.1 位带映射图 对于上图,我们往 Address0 地址写入 1,那么就可以达到往寄存器的第 0 位 Bit0 赋值 1 的目的。这里我们不想讲得过于复杂,因为位带操作在实际开发中可能只是用来 IO 口的输入输出还比较方便,其他操作在日常开发中也基本很少用。下面我们看看 sys.h 中位带操作的定义。 代码如下: //位带操作,实现 51 类似的 GPIO 控制功能 //具体实现思想,参考< //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 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 //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) //输入 以上代码的便是 GPIO 位带操作的具体实现,位带操作的详细说明,在权威指南中有详细讲解,请参考< 有了上面的代码,我们就可以像 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 文件夹内包含了 usart.c 和 usart.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 |
|
相关推荐
|
|
2105 浏览 1 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
1941 浏览 3 评论
4537 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
2084 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
2599 浏览 1 评论
小黑屋| 手机版| Archiver| 德赢Vwin官网 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 03:57 , Processed in 0.749489 second(s), Total 63, Slave 46 queries .
Powered by 德赢Vwin官网 网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
德赢Vwin官网 观察
版权所有 © 湖南华秋数字科技有限公司
德赢Vwin官网 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号