前言
这篇记录一下怎么调用标准库的函数来初始化一个串口,并调库实现发数据和收数据,以及串口收中断的使用。
越往深处学习越感觉其实32就是一个功能更加齐全和强大的MCU,其实跟51没有什么本质上的区别。很多设置的地方都是同质化的。比如需要用到某个中断,无非就是初始化一个结构体变量,然后给这个变量的成员配好初值再调用NVIC_Init这个初始化函数新建一个中断。
比如这里我需要用到一个串口接收中断,
static void USART1_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
新建一个NVIC_InitTypeDef的结构体变量,然后给这个变量的成员都配置好相应的值,我这里设置串口接收中断的中断优先级为分组2 主优先级为1 子优先级为1,然后写好当前要配置的是哪一个中断,当前配置的中断为USART1_IRQn,并且配置的这个中断是使能状态,最后调用NVIC(嵌套中断控制器)初始化函数NVIC_Init新建一个中断,并把刚刚配置的结构体的地址作为形参传入进去,库函数就会帮我们新建一个串口中断。
因为我是需要一个串口接收中断。根据手册描述,只要接收寄存器收到数据,USART_SR这个串口的状态寄存器中的RXNE(读数据寄存器非空 (Read data register not empty)) 这一位就会自动置一,如果开启了串口接收中断的话,这时候就会进入相应串口的中断服务函数。
写好配置串口中断的函数之后,在下面的串口初始化函数中调用它:
void USART1_Config(void)
{
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
//打开USART1 RX和TX的时钟 103R8T6 是PA9-TXD PA10-RXD
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//打开串口外设的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//将PA9 改为推挽复用
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//将PA10 改为浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
USART_InitStruct.USART_BaudRate = 115200; //波特率115200
USART_InitStruct.USART_WordLength = USART_WordLength_8b; //字长8bit
USART_InitStruct.USART_StopBits = USART_StopBits_1; //停止位 1bit
USART_InitStruct.USART_Parity = USART_Parity_No; //不校验
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //发送和接收都使能
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不使用硬件流控制
USART_Init(USART1,&USART_InitStruct);
USART1_NVIC_Config(); //配置串口中断的 分组、优先级等
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
USART_Cmd(USART1,ENABLE);
}
类似新建串口中断的方法,初始化串口的方法也大同小异。首先先新建两个结构体变量,一个用来配置IO,另一个用来配置串口。至于为什么调用一个Init的库函数就能新建好一个串口,这其实不用纠结,库函数其实本质上是根据用户传入的参来判断需要给内部的哪些寄存器赋怎样的值,如果我们使用LL库开发,那么这部分就需要用户自己来写,目前使用标准库,这种工作就交给库来做。
有个日后需要注意的地方,103R8T6的USART1是挂载在APB2时钟线上的,所以在配置USART1这个串口的时候需要开APB2这条时钟线上的USART1外设的时钟;USART2-5都是挂载在APB1这条时钟线上的,所以在使用串口2-5的时候要注意不要开错了时钟。
配置Pin脚就是调用GPIO_Init,配置串口就是调用USART_Init,不过USART_Init这个函数的第一个形参,库已经帮我们新建好了,直接填需要用哪一个USART就行。
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); 这一句就是配置串口中断的,因为能触发串口中断有很多种方法,比如发送寄存器空 中断、发送完成 中断、接收寄存器非空 中断 等等…这里就是把串口中断配置成 只有接收寄存器非空 产生一次中断 ,推测如果再同时开启一个 发送完成 中断,当两个条件任意成立一个,就会进入串口1中断服务函数里面,那么在服务函数里面就需要判断一下到底是哪个中断标志成立了产生的中断,再做相应的处理。这个功能就比51强大很多的,不同的组合可以玩出很多不同的效果。
这里还要另说一句,发送寄存器空了 并不代表数据就已经全部发送出去了,发送数据时,MCU会把数据先丢到发送寄存器去,然后发送寄存器再把数据丢到发送移位寄存器去,然后发送移位寄存器再把数据发送到编码模块发给上位机。框图如下:
最后就是开启串口这个外设的库函数 USART_Cmd(USART1,ENABLE);
相应的关闭串口就是 USART_Cmd(USART1,DISABLE);
在主函数初始化的时候调用这个串口初始化函数USART1_Config,就可以把串口外设开起来了。
此时上位机发送一次数据,串口接收到数据之后就会产生一次中断,中断服务函数就可以写为:
void USART1_IRQHandler(void)
{
uint8_t rece_data=0;
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET)
{
rece_data = USART_ReceiveData(USART1);
USART_SendData(USART1,rece_data);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET) {;}
}
}
中断回调函数的名字 USART1_IRQHandler 这个是在启动文件里面规定好的,如果自己没有写,库会默认帮我们编译库里面弱定义好的回调函数,内容就是while(1) 即一直停留在回调函数里面。
这个回调函数的内容就是,判断一下接收寄存器非空 这一位是否被置1了(即接收到数据了),如果确定收到数据,那么就调用USART_ReceiveData函数把数据从 数据寄存器(USART_DR) 这里面取出来,当我们在读USART_DR这个寄存器的时候,RXNE这一位自动清零。
把数据读出来之后,调用串口发送函数USART_SendData,把刚刚接收到的数据通过串口1又发送出去,发送期间一直调用USART_GetFlagStatus 获取一下串口1的USART_SR的TC位(发送完成标志位)是否还是0,如果为0则是没法送完,就一直阻塞在这等串口发送完毕,当串口发送完毕之后,发送完成标志位被置一,跳出循环。同时我们就可以在上位机的接收窗口看到刚刚发送了什么数据过去给MCU。
到这一步串口的基础功能就实现完毕了,野火教程里面根据上位机发送的内容来点亮不同的灯,其实就是用switch(或说是if else)根据收到的数据判断一下然后再把相应的IO拉高或是拉低,没什么难度就不写了。