赞
踩
STM32串口通信(STM32F103/STM32F407)
1.GPIO引脚复用AF机制
2.模块Clock时钟树,使能机制。(低功耗)
3.UART串口通信机制
4.NVIC中断配置机制
5.DMA搬移机制
6.Freertos串口
7.RTOS 串口中断设置
选择USART RX TX 引脚
PA9
PA10
复用端口的意义是SOC外设或者系统的模块的i/o功能有多路,需要配置到具体的引脚输出
引脚只是模块的输出通路,是工具人。
端口复用在datesheet中有描述,什么模块的端口事实上是指定的
PA9的复用AF7是串口TX。AF7系列是USART和I2S系列 引脚。
USART1 TX,RX的其中一条通路是PA9,PA10
另外一条通路是PB5 ,PB7
对于模块而言其功能的实现有多个PIN通路
对于PIN而言,他可能被多个模块链接
PIN越多可以同时实现模块功能就越多,复用通路只有一条,一但被选择那么其他的通路就无法联通。所以PIN越多,提供的外设能力就越大。
本节关键 Alternate function mapping
复用端口的配置
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF)
实现USART功能的复用需要选择复用AF7
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
配置PA9,PA10的串口功能
对于STMF103系列,没有AF功能配置通过下面的 配置。
2.模块时钟使能
三条总线AHB,APB1,APB2,模块挂在相应的总线上。没有用到的模块就不需要开启,所以每个模块都有单独的时钟使能。
挂载哪条总线上就有哪个控制/管理
AHB1所管理的模块
AHB2 所管理的模块
APB管理的设备
APB2 管理的设备时钟
数据手册系统架构章节,对每条总线上的设备进行了列表,可以直接查询。
多数情况下,在库函数里面查看会更加方便
3.串口通信机制
UART 模块的输入和输出
TX发送口,RX接收口
发送的数据从TX发出从RX口接收。所以数据要发送通过本模块的TX。数据要接收通过本模块的RX。
所以两个串口设备的连线入上图所示。
人说话,从口出,听声音从耳入。
我说话,从口出,声音进别人耳朵,别人听到。
逻辑非常清晰。线不能链接错
MCU串口模块属于CPU外设。在MCU片内,电平TTL机制
TTL电平范围小,不利于长距离传输,可以通过提高传输电压提高抗干扰能力常用RS232电平。
电平机制的转换一般MCU无法实现,所以需要中间的转换IC
线路的联通需要物理接口,所以又会有接口标准
接口和电平会随着时代的发展而变化,和使用的场景关系非常大。
很显然他们都是串口通信的一部分。所以通信具有分层机制
接口和电平属于物理层。
传输
起始位,告知接收方数据开始发送,原则是和无信号的时候要有区别。约定无信息传递时高电平。开始传递时给低电平,表示开始
开始位S本身不不带有传输的信息,是告知对方“后面数据来了,请开始接收”
可以互相约定数据一次传输多少位 可以为5,6,7,8一般而言选择8位,代表一个字节。
发送完成数据后,需要表示传输完成,那么前面约定无信息传递时高电平。所以配备一个停止位T。
就这样重复的一个字节一个字节的传输。其实是一个Bit一个Bit传输。
为了适应跟多的情况,传输数据位可以配置,停止位也可以配置长度,需要双方约定好即可。
USART模块
可配置为 16 倍过采样或 8 倍过采样,因而为速度容差与时钟容差的灵活配置提供了可能
传输检测标志:
— 接收缓冲区已满
— 发送缓冲区为空
— 传输结束标志
十个具有标志位的中断源:
— CTS 变化
— LIN 停止符号检测
— 发送数据寄存器为空
— 发送完成
— 接收数据寄存器已满
— 接收到线路空闲
— 溢出错误
— 帧错误
— 噪声错误
— 奇偶校验错误
TX ,RX外部引脚
发送数据,由外部(CPU、DMA)写入数据到发送数据寄存器(TDR),按位发送到TX端口
接收数据,RX数据按位移入接收移位寄存器,然后写入RDR,可以由CPU或者DMA读走。
TDR,RDR都是8位的,所以读满就要取走
发送控制器,接收控制器分别控制模块的 发送和接收功能。
中断控制识别传输的各种事件,上报中断待处理。
当发送使能位 TE 置 1 之后,发送器开始会先发送一个空闲帧 (一个数据帧长度的高电平),接下
来就可以往 USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待 USART 状
态寄存器 (USART_SR) 的 TC 位为 1,表示数据传输完成,如果 USART_CR1 寄存器的 TCIE 位置
1,将产生中断。
空闲帧
从起始位开始到结束位结束,测量时间大概87us 。波特率115200. 理论时间((1/115200 ) * 10 bit =86.8us)
这个时间87us是一帧的时间
空闲帧的开始是TX拉低的时候,也是大概87us
在发送数据时,编程的时候有几个比较重要的标志位我们来总结下。
如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索
起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成
后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置 1,同时如果
USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断。
为得到一个信号真实情况,需要用一个比这个信号频率高的采样信号去检测,称为过采样,这个
采样信号的频率大小决定最后得到源信号准确度,一般频率越高得到的准确度越高,但为了得到
越高频率采样信号越也困难,运算和功耗等等也会增加,所以一般选择合适就好。
在测量一个bit周期的时候,测量值8.91us 和理论时间8.68存在误差,这是由于采样频率造成的。频率越高就越能还原原始信号。
STM32F407 UART 可以进行16或者8倍的过采样频率。
传输完成或者接收完成需要通知外界,一般使用中断
也就是说,USART内部产生事件,要不要传递出去到NVIC中断控制器,由各自的使能位决定。
ARM cortex M4 NVIC中断控制器
USART 模块属于peripherals ,内部的事件使能后,以IRQs的形式传达给NVIC。
typedef struct { uint8_t NVIC_IRQChannel; /*!< Specifies the IRQ channel to be enabled or disabled. This parameter can be an enumerator of @ref IRQn_Type enumeration (For the complete STM32 Devices IRQ Channels list, please refer to stm32f4xx.h file) */ uint8_t NVIC_IRQChannelPreemptionPriority; /*!< Specifies the pre-emption priority for the IRQ channel specified in NVIC_IRQChannel. This parameter can be a value between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table A lower priority value indicates a higher priority */ uint8_t NVIC_IRQChannelSubPriority; /*!< Specifies the subpriority level for the IRQ channel specified in NVIC_IRQChannel. This parameter can be a value between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table A lower priority value indicates a higher priority */ FunctionalState NVIC_IRQChannelCmd; /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel will be enabled or disabled. This parameter can be set either to ENABLE or DISABLE */ } NVIC_InitTypeDef;
NVIC_IRQChannel
USART1_IRQn
上图称之为中断索引,当中断可以处理的时候,会通过索引去中断向量表上找到处理函数入口。
USART1 所有的中断,都是以USART1_IRQn的方式上报给NVIC。
USART内部的中断非常多,如何识别,通过
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT) { uint32_t bitpos = 0x00, itmask = 0x00, usartreg = 0x00; ITStatus bitstatus = RESET; /* Check the parameters */ assert_param(IS_USART_ALL_PERIPH(USARTx)); assert_param(IS_USART_GET_IT(USART_IT)); /* The CTS interrupt is not available for UART4 and UART5 */ if (USART_IT == USART_IT_CTS) { assert_param(IS_USART_1236_PERIPH(USARTx)); } /* Get the USART register index */ usartreg = (((uint8_t)USART_IT) >> 0x05); /* Get the interrupt position */ itmask = USART_IT & IT_MASK; itmask = (uint32_t)0x01 << itmask; if (usartreg == 0x01) /* The IT is in CR1 register */ { itmask &= USARTx->CR1; } else if (usartreg == 0x02) /* The IT is in CR2 register */ { itmask &= USARTx->CR2; } else /* The IT is in CR3 register */ { itmask &= USARTx->CR3; } bitpos = USART_IT >> 0x08; bitpos = (uint32_t)0x01 << bitpos; bitpos &= USARTx->SR; if ((itmask != (uint16_t)RESET)&&(bitpos != (uint16_t)RESET)) { bitstatus = SET; } else { bitstatus = RESET; } return bitstatus; }
这样可以获知USART内部具体是发生了什么事件而产生中断,在去做对应的处理。
NVIC所管理的称为全局中断。
NVIC的使能比较简单,因变对NVIC来说只有USART1_IRQn.不需要关注USART内部的各种事件。
NVIC_InitStructure_uart.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure_uart.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure_uart.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure_uart.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure_uart);
定义中断处理函数
在启动文件中 ; External Interrupts 写明了USART产生的中断服务函数 叫 USART1_IRQHandler
我们可以去修改这个函数名称,不过不建议这么做,保持S文件的原始性,也方便代码移植。
可以通过define的方式来定义中断服务函数。
一般来说,接收数据完成后会产生中断,这个时候把数据从寄存器中拿出来,放入我们准备好的缓存当中,这个时候数据是一个字节。
一个字节对于我们来说可能是一段话的而一部分,需要等待多个字节来组合一段有意义的内容。
所以把它占时放在缓存中
我们可以在缓存中对数据进行判断,识别数据包,或者一句话等等
unsigned char buff_index=0; void USART1_IRQHandler(void) { uint8_t ucTemp; if (USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) { ucTemp = USART_ReceiveData( USART1 ); UART1_BUFFER[buff_index] = ucTemp; buff_index++; if(buff_index > UART1_BUFFER_SIZE) { buff_index = 0; memset(UART1_BUFFER,0,sizeof(UART1_BUFFER)); } } }
启用在线调试,观察UART1_BUFFER的内容。
0x0D(ascii码是13) 指的是“回车” \r是把光标置于本行行首
0x0A(ascii码是10) 指的是“换行” \n是把光标置于下一行的同一列
DMA数据搬移
串口的USART框图如图所示。数据寄存器(发送数据,接收数据寄存器)只能通过CPU或者DMA访问。
CPU访问数据寄存器的流程:数据寄存器接收满或者发送完毕会产生事件,事件传入NVIC中断模块后上报给CPU,CPU通过中断服务读取。这条链路在执行就会使用CPU,增加系统的负担。如果通过DMA可以在CPU不参与的情况下自己搬运数据,就好像不执行指令就能
做事一样。DMA模块本身的内容比较复杂,这里注重实现。
DMA的初始化结构体比较长,最后会附上dma的初始化代码。
DMA_InitTypeDef dma_init_struct;
dma_init_struct.DMA_Channel = DMA_Channel_4;
DMA的信号通道选择和GPIO的复用结构有点相识。
DMA本身只管理STREAM0~STREAM7,只有这8条通路,然后STREAMx(x = 1,2,3,…8)通过多路复用器,和REQ_STRx_CH链接。
REQ_STRx_CH负责链接到具体的外设。
串口USART1 RX在数据流5(STREAM5)的通道4上。所以dma_init_struct.DMA_Channel = DMA_Channel_4;
初始化USART1 的代码如下所示
static void DMA_config(void) { DMA_InitTypeDef dma_init_struct; extern unsigned char UART1_BUFFER[120]; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_DeInit(DMA2_Stream5); while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) { } dma_init_struct.DMA_Channel = DMA_Channel_4; dma_init_struct.DMA_BufferSize = 120; dma_init_struct.DMA_DIR = DMA_DIR_PeripheralToMemory; dma_init_struct.DMA_FIFOMode = DMA_FIFOMode_Disable; dma_init_struct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; dma_init_struct.DMA_Memory0BaseAddr = (unsigned int)UART1_BUFFER; dma_init_struct.DMA_MemoryBurst = DMA_MemoryBurst_Single; dma_init_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; dma_init_struct.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Byte; dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable ; dma_init_struct.DMA_PeripheralInc =DMA_PeripheralInc_Disable ; dma_init_struct.DMA_Mode =DMA_Mode_Circular; dma_init_struct.DMA_PeripheralBaseAddr =(USART1_BASE+0x04); dma_init_struct.DMA_PeripheralBurst =DMA_PeripheralBurst_Single; dma_init_struct.DMA_Priority = DMA_Priority_Medium; DMA_Init(DMA2_Stream5, &dma_init_struct); DMA_Cmd(DMA2_Stream5, ENABLE); while (DMA_GetCmdStatus(DMA2_Stream5) != ENABLE) { } }
把外围 设备的数据放到内存的BUFFER
dma_init_struct.DMA_DIR = DMA_DIR_PeripheralToMemory;
第一步把DR数据寄存器的数据取出来
1.DR寄存器地址
dma_init_struct.DMA_PeripheralBaseAddr =(USART1_BASE+0x04);
2.一次取多少数据,总共取多少次,取满次数怎么办
dma_init_struct.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Byte;
dma_init_struct.DMA_BufferSize = 120;
dma_init_struct.DMA_Mode =DMA_Mode_Circular;
3。下一次取要不要取另外地址的数据
dma_init_struct.DMA_PeripheralInc =DMA_PeripheralInc_Disable ;
第二部 DMA获得数据
第三部 DMA写入数据到目标内存
1.内存地址
dma_init_struct.DMA_Memory0BaseAddr = (unsigned int)UART1_BUFFER;
2.写入地址会不会变化
dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable ;
根据以上的逻辑可以理清楚DMA结构体初始化的过程。
最后在主函数中使能 UART DMA
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
1.对面发送端的TX完成了一帧的数据传输,USART 接收完成标志RXNE置位,
2.DMA收到请求源请求
3,读取USART DR寄存器数据值
关掉CPU搬移数据。
最后开启调试,全速运行。
OS下的USART
UART的数据接收是通过硬件进行,硬件没有缓存,所以一但接收数据寄存器满,就要及时的取出来
这个时候有两种方法
1.NVIC 中断服务函数处理
void USART1_IRQHandler(void) { #if 1 uint8_t ucTemp; if (USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) { ucTemp = USART_ReceiveData( USART1 ); UART1_BUFFER[buff_index] = ucTemp; buff_index++; if(buff_index > UART1_BUFFER_SIZE) { buff_index = 0; memset(UART1_BUFFER,0,sizeof(UART1_BUFFER)); } } #endif }
串口没有包的概念,所以判断数据流的起停,本身是没有办法的。
每一次接收1byte 都会产生中断
中断内容至少包含数据搬移,一般放入FIFO中
FIFO主要作用是做数据缓存
那么如何做数据的解析,如果每次收到1byte都唤起应用层任务,显然 是不明智的
在没有流量控制引脚的状态下
1.做包协议,规定包头包尾,在每次拿到数据后检查包传输是否完成。完成后,发送信号量通知应用层任务。
2.规定一次传输的字节数,中断计数。计数完成后发送信号量通知应用层任务。
void uart_rx_isr(uart_device_t *dev) { int32_t ch = -1; if (!dev) { return; } hw_interrupt_disable(); while (1) { ch = dev->ops->getc(dev); if (ch == -1) { break; } ch &= 0xFF; sw_fifo_put(&dev->rx_fifo, (const uint8_t *)&ch, 1, SW_FIFO_TYPE_COVER); if (dev->rx_indicate.cb) { dev->rx_indicate.cb(dev->rx_indicate.para); } } hw_interrupt_enable(); }
dev->rx_indicate.cb是一个回调函数,当接收数据后执行一个函数,这个时候可以做数据的处理或者发送信号量。
2.使用DMA处理
数据从DR寄存器拿出来后放入指定的缓存,这个时候是否要通知CPU来对数据进行判别
数据是否可用。串口UART是基本的通信,它只有帧的概念 也就是起始位和结束位之间的数据。
没有数据包的概念。起始位和结束位之间的数据一般是一个字节
DMA传输有事件中断
开启相应的中断,在中断函数中处理搬运。
static void DMA_config(void) { DMA_InitTypeDef dma_init_struct; extern unsigned char UART1_BUFFER[120]; NVIC_InitTypeDef DMA_IT_init; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_DeInit(DMA2_Stream5); while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) { } dma_init_struct.DMA_Channel = DMA_Channel_4; dma_init_struct.DMA_BufferSize = 20; dma_init_struct.DMA_DIR = DMA_DIR_PeripheralToMemory; dma_init_struct.DMA_FIFOMode = DMA_FIFOMode_Disable; dma_init_struct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; dma_init_struct.DMA_Memory0BaseAddr = (unsigned int)UART1_BUFFER; dma_init_struct.DMA_MemoryBurst = DMA_MemoryBurst_Single; dma_init_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; dma_init_struct.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Byte; dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable ; dma_init_struct.DMA_PeripheralInc =DMA_PeripheralInc_Disable ; dma_init_struct.DMA_Mode =DMA_Mode_Circular; dma_init_struct.DMA_PeripheralBaseAddr =(USART1_BASE+0x04); dma_init_struct.DMA_PeripheralBurst =DMA_PeripheralBurst_Single; dma_init_struct.DMA_Priority = DMA_Priority_Medium; DMA_Init(DMA2_Stream5, &dma_init_struct); DMA_Cmd(DMA2_Stream5, ENABLE); while (DMA_GetCmdStatus(DMA2_Stream5) != ENABLE) { } DMA_ITConfig(DMA2_Stream5,DMA_IT_TC,ENABLE); DMA_IT_init.NVIC_IRQChannel=DMA2_Stream5_IRQn ; DMA_IT_init.NVIC_IRQChannelCmd=1; DMA_IT_init.NVIC_IRQChannelPreemptionPriority=1; DMA_IT_init.NVIC_IRQChannelSubPriority = ENABLE; NVIC_Init(&DMA_IT_init); }
串口部分的设计如果每次传输1byte都进行中断,或者没 1byte进行字符解析,会占用大量的CPU资源。
使用DMA可以非常高效的解决CPU使用率的问题
操作系统在进行关键性操作的时候需要进入临界代码断,其目的是在进行内核数据读写的时候,保证内核数据不被篡改以及数据读写完整性。简单说就是操作系统的一些API在执行的时候会关掉一部分中断。
关掉的中断,会有一个最大优先级。为什么不完全关掉中断呢,为了保证高优先级的中断服务不被打断,减少抖动。
因此操作系统都会有一个 OS可屏蔽最大中断优先级。
意思就是在操作系统进行关键性操作的时候,会关掉这个中断优先级以下的中断。
同样,高于这个优先级的中断中不能调用OS API 函数。
也就是说 串口的中断优先级设定不可以高于 OS可屏蔽最大中断优先级
以freertos 为例
configKERNEL_INTERRUPT_PRIORITY 为systick 的中断优先级
configMAX_SYSCALL_INTERRUPT_PRIORITY 为 OS可屏蔽最大中断优先级
NVIC_IRQChannelPreemptionPriority > configMAX_SYSCALL_INTERRUPT_PRIORITY
中断优先级来说配置的数字越小,优先级越高。必须满足上面的不等式。
首先一个中断发生后Cortex M3 内核PSR寄存器
ISR记录了中断号,也就是中断向量表的索引位置。
中断向量表的53号中断
__Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog DCD PVD_IRQHandler ; PVD through EXTI Line detect DCD TAMPER_IRQHandler ; Tamper DCD RTC_IRQHandler ; RTC DCD FLASH_IRQHandler ; Flash DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line 0 DCD EXTI1_IRQHandler ; EXTI Line 1 DCD EXTI2_IRQHandler ; EXTI Line 2 DCD EXTI3_IRQHandler ; EXTI Line 3 DCD EXTI4_IRQHandler ; EXTI Line 4 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 DCD ADC1_2_IRQHandler ; ADC1_2 DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN1_SCE_IRQHandler ; CAN1 SCE DCD EXTI9_5_IRQHandler ; EXTI Line 9..5 DCD TIM1_BRK_IRQHandler ; TIM1 Break DCD TIM1_UP_IRQHandler ; TIM1 Update DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare DCD TIM2_IRQHandler ; TIM2 DCD TIM3_IRQHandler ; TIM3 DCD TIM4_IRQHandler ; TIM4 DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD I2C2_EV_IRQHandler ; I2C2 Event DCD I2C2_ER_IRQHandler ; I2C2 Error DCD SPI1_IRQHandler ; SPI1 DCD SPI2_IRQHandler ; SPI2 DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD USART3_IRQHandler ; USART3 DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
53号是USART1_IRQHandler ,然后从优先级寄存器中读取 优先级和OS可屏蔽的最大优先级做比较。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。