900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > stm32f103——中断——UART中断服务函数

stm32f103——中断——UART中断服务函数

时间:2019-03-07 17:37:57

相关推荐

stm32f103——中断——UART中断服务函数

在程序中,CPU对外界突发事件进行处理的方式又两种:

1》轮询系统:(在main中,使用while循环,进行循环判断外界事物是否发生)

while(1){

}

2》前后台系统:(此时main中的while中的程序是在处理其它事务,当中断来到时,就处理中断服务函数)

轮询系统+中断

中断的过程:

在主程序执行的过程中,中断突然发生,此时主程序停止往下执行,并将CPU的当前状态保持在内核栈中(即:现场保护)。然后跳转到中断服务函数的入口,并执行中断服务函数,当中断服务函数执行完后,再将之前保存在内核栈中的状态全部进行出栈,将状态恢复到发生中断之前(即:现场恢复),此时对于CPU来说,就好像从来没有发生过中断一样。

中断管理器——异常处理器NVIC

我们可以在这里寻找到,NVIC的中断优先级分组。

中断异常处理器-----NVIC---专门用于管理中断的片上外设

管理中断的方式:(注意:整个程序,管理中断的方式,只能五选其一,如果设置多个,那么只有最后一个设置生效)

*NVIC_PriorityGroup_0:0 bits for pre-emption priority //没有主优先级,即:0个位来表示主优先级

* 4 bits for subpriority //16种次优先级,即:4个位来表示次优先级

*NVIC_PriorityGroup_1:1 bits for pre-emption priority//2种主优先级,即:1个位来表示主优先级

*3 bits for subpriority //8种次优先级,即:3个位来表示主优先级

NVIC_PriorityGroup_1的分配如下图:

*NVIC_PriorityGroup_2:2 bits for pre-emption priority

*2 bits for subpriority

*NVIC_PriorityGroup_3:3 bits for pre-emption priority

*1 bits for subpriority

*NVIC_PriorityGroup_4:4 bits for pre-emption priority

*0 bits for subpriority

NVIC给每个中断源都配置两种优先级,分别是:(数值越小优先级越高)

抢占优先级(主优先级):具备抢占功能,优先级高的中断可以抢占优先级低的中断,但是一个中断不能抢占另外一个相同优先级的中断。由此可知,一个中断不能抢占它自己,因为自己与自己的优先级相同。

执行优先级(次优先级):不具备抢占功能,只有在两个主优先级相同的中断,并且它们同时发生的情况下,那么次优先级高的先执行。

总结:配置中断的3个步骤(不管什么时候,中断配置都是这四步)

1.配置中断源

2.配置中断源的抢占和执行优先级

3.编写中断服务函数

4.如果是程序的第一个中断,需要设置优先级分组(最好写在主程序中)

配置中断的4步细节描述:(以UART中断为例)

配置一个中断需要做以下几件事情:

1,配置中断源

void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)

2,给这个中断源配置相应的抢占优先级和执行优先级

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)

typedef struct

{

uint8_t NVIC_IRQChannel; //中断通道号 refer to stm32f10x.h

//typedef enum IRQn

uint8_t NVIC_IRQChannelPreemptionPriority; //主优先级

uint8_t NVIC_IRQChannelSubPriority;//次优先级

FunctionalState NVIC_IRQChannelCmd; //使能中断通道

} NVIC_InitTypeDef;

3,编写中断服务函数----中断服务函数的函数名,已经在启动文件中定义,所以中断服务函数的函数名在启动文件中找

注意:中断服务函数写在,stm32f10x_it.c文件中,该文件在apps文件夹下

void USART1_IRQHandler(void)

{

//判断标志位

//清除中断标志位

}

4,如果是程序的第一个中断,需要设置优先级分钟(最好写在main函数中,写在子函数中会被子函数重复调用)

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)

怎样查看中断源的通道号:

我们可以在stm32f10x.h文件中找到,在与我们所使用的启动文件名字(即:STM32F10X_HD)一样的那部分定义。再到需要使用的中断源的名字,它的右边便是中断通道号。我们还注意到了这部分的通道号是从18开始的,是因为这些通道号是这款芯片专用的通道号,0~17通道号在该文件的前面,它是通用的通道号。

注意:中断服务函数的函数名不能乱写。必须在启动文件中寻找到中断服务函数的名字。因为函数名就是函数的入口地址,这个地址在启动文件中就定义好了的。中断服务函数的函数名如果写错了会导致中断服务函数无法执行。

UART中断配置函数

UART中断源配置函数。

USART_IT_RXNE:表示当UART串口接收到数据的时候,就触发UART中断。

中断标志位,如果相应的事件发生中断,则该标志位置1

NVIC中断管理器配置函数。

UART接收数据函数,即UART_RX接收数据进CPU来。

UART发送数据函数,即UART_TX发送数据出去。

编写程序

void USART1_Config(void)

{

GPIO_InitTypeDef GPIO_InitStruct;

USART_InitTypeDef USART_InitStruct;

NVIC_InitTypeDef NVIC_InitStruct;

// 1,打开时钟---GPIOA,串口1,AFIO

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_USART1,ENABLE);

// 2,GPIO初始化

GPIO_InitStruct.GPIO_Pin =GPIO_Pin_9;

GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;

GPIO_InitStruct.GPIO_Speed =GPIO_Speed_2MHz;

GPIO_Init(GPIOA,&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin =GPIO_Pin_10;

GPIO_InitStruct.GPIO_Mode =GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA,&GPIO_InitStruct);

// 3,串口初始化

USART_InitStruct.USART_BaudRate =115200;

USART_InitStruct.USART_HardwareFlowControl =USART_HardwareFlowControl_None;

USART_InitStruct.USART_Mode =USART_Mode_Rx|USART_Mode_Tx;

USART_InitStruct.USART_Parity =USART_Parity_No;

USART_InitStruct.USART_StopBits =USART_StopBits_1;

USART_InitStruct.USART_WordLength=USART_WordLength_8b;

USART_Init(USART1,&USART_InitStruct);

USART_ClearFlag(USART1, USART_FLAG_TC|USART_FLAG_TXE);

// 1,配置中断源

USART_ITConfig(USART1,USART_IT_RXNE, ENABLE); //当UART串口接收到数据的时候,就触发UART中断。

// 2,给这个中断源配置相应的抢占优先级和执行优先级

NVIC_InitStruct.NVIC_IRQChannel =USART1_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =0;

NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;

NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE;

NVIC_Init(&NVIC_InitStruct);

// 4,使能串口

USART_Cmd(USART1,ENABLE);

}

//中断服务函数(写在stm32f10x_it.c文件中的指定位置,这个位置如下图)

该文件的这个位置是专门写外设中断的地方,其中USART1_IRQHandler为中断函数名

/*中断服务函数:功能:pc端的串口助手通过UART_RX发送数据给MCU,接收到数据后,MCU通过UART_TX发送数据给PC端的串口助手*/

void USART1_IRQHandler(void) //

{

if(SET==USART_GetITStatus(USART1,USART_IT_RXNE)){ //如果UART串口接收到了数据,那么该标志位置1

USART_ClearITPendingBit(USART1,USART_IT_RXNE); //手动清空标志位,一定要清空!

USART_SendData(USART1,USART_ReceiveData(USART1)); //通过UART_TX发送数据给PC端的串口助手

}

}

注意:无论什么中断,在进入中断后必须进行中断标志位的判断,即:

if(SET==USART_GetITStatus(USART1,USART_IT_RXNE)).....,因为由上面可知,触发USART1中断的事件有很多种,我们并不知道是哪一种事件触发了USART1中断,所以,我们需要对相应的中断触发事件标志位进行判断,然后做相应的中断程序处理。

注意:中断服务函数越短越好。如果是定时器中断,那么定时器中断服务函数执行的时间大于定时的时间的话,每当执行完定时器中断函数后,就立马又加入该中断。因为定时器中断在进入中断后,就立马重新开始计数,当计数完后,触发下一次中断,但是如果此时中断函数还没有执行完,那么定时器不会马上执行下一次中断(因为自己不能抢占自己),并且计数器此时不会再重新计数,而是等待中断函数执行后,立马又进入定时中断,然后计算器重新开始计数。这样的话,其他中断就没机会抢到cpu的控制权了。就算不是定时器中断,那么如果中断执行时间长的话,main函数就没时间去执行了,而且其他中断也没有机会抢到cpu的控制权。所以,在中断中不要加mS级的延时(中断执行时间都是uS级的)!!!。

注意:外部中断触发也是如此,如果在触发中断的时候,上一次中断还没执行完,由于自己不能抢断自己,它就会等待。等到上一次中断执行完后,它就马上执行这一次的中断。

int main(void)

{

RCC_ConfigTo72M();//将系统时钟配置成72MHZ

Systick_Config(72);

USART1_Config();

// 4,如果是程序的第一个中断,需要设置优先级分钟

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

while(1){

LED_CTRL(LEDR,LED_CLOSE);

Systick_NmsDelay(3000);

LED_CTRL(LEDR,LED_OPEN);

Systick_NmsDelay(3000);

}

}

串口助手给MCU发送字符,控制LED的亮/灭

功能:串口助手给MCU发送open字符串,控制LED的亮,发送close字符串,控制LED的灭

方法一:(接收标志位中断)

//串口中断服务函数

/*每次MCU接收到从串口助手发送来的一个字节,MCU就会进入一次USART1中断服务函数。*/

void USART1_IRQHandler(void)

{

if(SET==USART_GetITStatus(USART1,USART_IT_RXNE)){

USART_ClearITPendingBit(USART1,USART_IT_RXNE);//手动清空标志位,一定要清空!

USART_SendData(USART1,USART_ReceiveData(USART1));

USART1_Data[indexs]=USART_ReceiveData(USART1); //将接收到的一个字节数据,保存在数组USART1_Data[]中

indexs++; //数组下标后移

USART1_Data[indexs]='\0'; //让接收到的最末尾字符的后面为'\0'

if((USART1_Data[0]!='o')&&(USART1_Data[0]!='c')){ /*如果串口助手发送过来的第一个字符既不是字母‘o’也不是字母‘c’,那么就没必要再进行下去了,数组USART1_Data[]的索引回到0*/

indexs=0;

}else{

if(indexs>3){ //open为4个字符,如果串口助手发送过来4个字符,那么有可能发送过来的是open

if(0==strcmp("open",(const char *)USART1_Data)){ //数组中的内容与open字符串进行对比

LED_CTRL(LEDR,LED_OPEN); //开灯

indexs=0;

memset(USART1_Data,0,sizeof(USART1_Data));

}

if(0==strcmp("close",(const char *)USART1_Data)){ //数组中的内容与close字符串进行对比

LED_CTRL(LEDR,LED_CLOSE); //关灯

indexs=0;

memset(USART1_Data,0,sizeof(USART1_Data));

}

}

}

if(indexs==5){ /*如果前面对比失败,说明串口助手发送过来的即不是open也不是close。又因为close为5个字符,串口助手发送过来的字符数量为5个,数组索引回归0*/

indexs=0;

memset(USART1_Data,0,sizeof(USART1_Data));

}

}

}

方法二:(空闲总线中断)

//串口中断服务函数

void USART1_IRQHandler(void)

{

if(SET==USART_GetITStatus(USART1,USART_IT_RXNE)){

USART_ClearITPendingBit(USART1,USART_IT_RXNE);

USART_SendData(USART1,USART_ReceiveData(USART1));

USART1_Data[indexs]=USART_ReceiveData(USART1);

if(USART1_Data[indexs]==0x0D || USART1_Data[indexs]==0x0A) /*0XOD是回车,0XOA是换行,串口调试助手会在每帧数据末尾,发回车+换行符,我们需要把它两个清除*/

USART1_Data[indexs] = '\0';

indexs++;

}

if(SET==USART_GetITStatus(USART1,USART_IT_IDLE)){ //空闲总线中断

USART_ReceiveData(USART1);//清除空闲总线标志位

if(0==strcasecmp("open",(const char *)USART1_Data)){

LED_CTRL(LEDR,LED_OPEN);//开灯

}

if(0==strcasecmp("close",(const char *)USART1_Data)){

LED_CTRL(LEDR,LED_CLOSE);//关灯

}

indexs = 0;

memset(USART1_Data,0,sizeof(USART1_Data));

}

}

我们可以看到方法二更加的方便和简洁。那么空闲总线中断是什么?

USART1,USART_IT_IDLE:空闲总线中断标志位,当MCU的UART接收完一帧数据后,该标志位就会置1。也就是,串口助手发送一串数据给MCU。MCU则是一个字节一个字节的接收。当接收完这一串数据后,过一小段时间,如果没有数据发送过来,那么空闲总线中断标志位就会自动置1,表示MCU将这一帧数据接收完成。

单片机串行通信里面的数据帧是怎么理解?一帧数据的位数可以改变吗?

串行通信中,帧信息一般是根据需要自己约定而确定的。其内容一般是由多个8位单字节数据组成,比如你所说的传感器,需要采集电压值,电流值等信息,假设这些信息需要10个字节,那么你的一帧信息最少需要10个字节,也就是收发两方都需要计数,计数到10时才能说明通讯完成。这是最简单的,但大多数应用中规范的做法一帧信息都会包含帧头标识符、帧长度、信息内容及校验信息。

注意:在中断配置中如果打开了中断源,那么一定要在中断服务函数中手动将中断标志位清除。否则,中断触发的时候,标志置1,当再次进入到中断后,由于上一次中断没有清除标志位,那么程序就可能会卡在中断服务函数中。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。