STM32学习之使用库函数编写串口通信

注:使用的芯片是STM32F103ZET6

一、串口初始化部分的程序

1.初始化时钟

  因为要使用串口1这个外设,因此在使用之前就需要开启串口1的时钟,并且之后还需要设置IO输入输出的模式,在这里我们也需要开启GPIO的时钟。程序如下:

//初始化串口时钟
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
 //初始化GPIO时钟
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE)

  可是为什么要初始化时钟呢在《STM32中文参考手册_V10》的RCC中有这样一句话“当外设时钟没有启用时,软件不能读出外设寄存器的数值,返回的数值始终是0x0。”,也就是只有开启了对应外设的时钟,才能操作外设。
  该部分解释参考如下文档:
https://blog.csdn.net/dp29sym41zygndvf/article/details/82321133

2. 初始化GPIO

  初始化GPIO就是设置使用端口的引脚、模式以及速度。因为使用的是串口1,所以在STM32F103ZET6芯片上USART_TX、USART_RX分别对应的引脚是PIOA.9和GPIOA.10。引脚复用的功能不同,对应引脚模式设置就不同,详细的可以参考《STM32中文参考手册_V10》的8.1.11,下表就是使用参考手册里的表21 USART。

这里是引用《STM32中文参考手册》里的表21截取《STM32中文参考手册》里的图

  一般都设置串口的配置为全双工模式,因此设置GPIOA.9的模式为推挽复用输出,设置GPIOA.10的模式为浮空输入。引脚的速度没有太大的限制。该端程序如下:

GPIO_InitTypeDef GPIO_InitStruct;

//初始化GPIO
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;  //GPIOA.9是USART1的TX
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //设置其模式为推挽复用输出
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz; //速度为10MHz
 GPIO_Init(GPIOA,&GPIO_InitStruct);
 
 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;  //GPIOA.10是USART1的RX
 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; //设置其模式为浮空输入
 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;  //速度为10MHz
 GPIO_Init(GPIOA,&GPIO_InitStruct);

3. 初始化串口

  初始化串口就是设置串口的停止位、奇偶校验位、字长、波特率、串口的模式以及硬件流控制。
   停止位:可以设置停止位为0.5个、1个、1.5个和2个停止位,当通常都设置1个停止位。
  字长:可以设置8个数据位或9个数据位。
  奇偶校验位:当设置有8个数据位时,就没有奇偶校验位;当设置有9个数据位时,第9位为奇偶校验位,以保证传送数据的准确性。如果设置为奇校验,如果前8个数据位中1的个数为奇数,则该位为0,如果为偶数,该位为1。若为偶校验,如果1的个数为偶数该位为0,为奇数该位为1。
  波特率:就是数据传输的速率,接收方的波特率要与发送方的波特率相同。
  串口的模式:设置发送使能和接受使能
  硬件流控制:这个只有在半双工时使用,一般不开启这个功能。
  该段程序如下:

USART_InitTypeDef USART_InitStruct;

//初始化串口
 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;  //1位停止位
 USART_InitStruct.USART_WordLength = USART_WordLength_8b; //8位数据位
 USART_Init(USART1,&USART_InitStruct);

4.串口使能

  之前都只是设置串口的一些配置,并没有使串口开始工作,因此我们要使能串口,需要调用USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)这个函数,其最终是对控制寄存器1(USART_CR1)的位13的操作,如果该位为1,USART模块使能,若为0,禁止USART分频器和输出。
  该段程序如下:

//使能串口
 USART_Cmd(USART1,ENABLE);

  通常接受数据需要使用串口中断,而串口发送可以不使用串口中断。如果不需要开启串口中断,到这一步已经完成串口的初始化部分。如果需要用到串口中断,则再需要下面两个步骤。

5.初始化NVIC

  首先就是了解一下NVIC是什么,NVIC,就是Nested vectored interrupt controller,即嵌套向量中断控制器。
  在STM32F103ZET6芯片中有16个内核中断,60个可屏蔽中断,16个可编程中断优先级,怎么多中断怎么管理呢,这时候NVIC嵌套向量中断管理器就派上用场了。首先先对中断进行分组,如下表所示:

AIRCR[10:8] IP bit[7:4]分配情况 分配结果
0 111 0:4 0位抢占优先级,4位响应优先级
1 110 1:3 1位抢占优先级,3位响应优先级
2 101 2:2 2位抢占优先级,2位响应优先级
3 100 3:1 3位抢占优先级,1位响应优先级
4 011 4:0 4位抢占优先级,0位响应优先级

注:该表是参考正点原子第24讲 NVIC中断优先级管理的PPT
  这里所说的中断分组并不是说把各个中断分到了这几个组,而是说每一个组对应的抢占优先级和响应优先级的位数不同,根据自己需要多少位抢占优先级和几位响应优先级来设置,总共有4个位来控制。
  抢占优先级的讲解:抢占优先级高的中断可以打断抢占优先级低的中断,这就有点像51的中断优先级,0的时候抢占优先级最高。
  响应优先级的讲解:响应优先级高的不能打断响应优先级低的,只有在抢占优先级相同时起作用,当抢占优先级相同时,响应优先级高的,当和响应优先级低的中断相同时发生时,响应优先级高的中断先触发。
  上面说到总共有4个位来设置这两个优先级,因此24就为16,就有16级可编程中断优先级。
  举个例子
  我要设置串口1的中断,其抢占优先级为2,响应优先级也为2。
  1.先设置中断分组。
  因为抢占优先级为2,则不能用组1,因为组1可设置的抢占优先级为0位,不可以设置,所以应该从组1到4选择,因为响应优先级为2,则不能用组4,组4的响应优先级没有分配位数,因此可以从组2到3选择,我选择组2。
  2.设置优先级。
  因为组2,抢占优先级分配2个位,响应优先级分配2个位,因此抢占优先级可以设置22个也就是4个优先级等级,响应优先级也是4个等级,即0到3。要设置抢占优先级为2,响应优先级也为2,把2写入NVIC_IP寄存器的位6和位7,设置抢占优先级;把2写入NVIC_IP寄存器的位4和位5,设置响应优先级优先级。
  在我们使用库函数编写程序时,void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);是进行中断分组,NVIC_Init(&NVIC_InitStruct);是对NVIC初始化,也就是设置抢占优先级,响应优先级,IRQ的串口通道打开和IRQ通道使能这四个参数。
  抢占优先级和响应优先级是几设置即就行。
  为什么要打开IRQ对应的串口1通道,因为IRQ是interrupt
request,即中断请求通道,也就是选择什么来触发中断,因为使用的是串口1,因此需要开启IRQ的串口1通道,选择使用串口1触发。使能串口1的IRQ通道就不用说了。
  该段程序如下:

NVIC_InitTypeDef NVIC_InitStruct;

//初始化NVIC(设置抢占优先级和响应优先级)
 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;  //选择IRQ为USART1
 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能IRQ通道
 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; //设置抢占优先级
 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; //设置响应优先级
 NVIC_Init(&NVIC_InitStruct);  

  中断分组有关的库函数放在了主函数刚开始,没在在这部分程序中。

6.开启串口1中断

  在之前的步骤中把有关于串口的配置都配置好了,而这一步是设置串口1是通过什么触发中断的,比如是发送完成后触发中断还是接受到数据触发中断。在库函数中void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);是开启串口1中断的,这个函数第二个形参就是设置串口是通过哪种方式触发中断的,一般情况下我们使用的是USART_IT_RXNE,就是设置当接收到数据触发中断。
  该段程序如下:

USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //设置接受到数据触发中断

  之前设置串口1的IRQ通道,只是设置了是通过串口1来触发中断,也就是中断源,但具体是由串口1的哪种方式来控制触发中断,就需要这一步设置。

  到此,串口初始化已经全部配置完成。

二、中断服务函数部分

  由于这个程序只是编写一个简单的串口收发,所以在中断服务函数部分就编写了收到什么数据就发送什么数据,程序如下:

void USART1_IRQHandler(){  //串口1中断服务函数
 u8 Recv;
 if(USART_GetITStatus(USART1,USART_IT_RXNE) != RESET){
  Recv = USART_ReceiveData(USART1);
  USART_SendData(USART1,Recv);
 }
}

  进入串口1的中断服务函数后,先通过调用USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)这个函数来判断是不是因为接收到数据而触发中断的,或许该奇怪了,之前明明设置的就是接收到数据后触发中断,为什么进中断服务函数还需要再判断,这是因为虽然选择中断的触发方式,但是可能是别的情况触发,下面引用《STM32中文参考手册_V10》25.4中断请求的话来解释一下:

  USART的各种中断事件被连接到同一个中断向量,有以下各种中断事件:
● 发送期间:发送完成、清除发送、发送数据寄存器空。
● 接收期间:空闲总线检测、溢出错误、接收数据寄存器非空、校验错误、 LIN断开符号检测、噪音标志(仅在多缓冲器通信)和帧错误(仅在多缓冲器通信)。
  如果设置了对应的使能控制位,这些事件就可以产生各自的中断 。通用同步异步收发器(USART)

  因此需要先判断是否为是对应的标志位置一而触发的中断,之后就是调用库函数中串口接收数据的函数,读出接受的数据,为了能看到现象,所以又调用库函数中串口发数据函数,将数据发出去,在电脑的串口小助手中就可以看到接收串口中的数据和发送框中数据一致。

三、主函数部分

  因为实现的功能比较简单,所以在主函数中主要调用初始化函数。主函数部分如下:

int main(){
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组
   Uart_Init(); //初始化串口

   while(1);
} 

  刚开始是调用中断分组的函数,这部分讲解在初始化NVIC部分中解释了,之后就是调用初始化串口函数,这个函数是自己编写的不是库函数里的,这个函数就是包含上面第一部分串口初始化部分的程序中所有列出来的语句。然后就是使用while死循环,使程序一直跑下去。

  该篇文章是我学习正点原子STM32视频讲解之后的总结和自我理解,如果有什么不对的,还请各位指教。

参考资料
《STM32中文参考手册_V10》
《STM32F10xxx Cortex-M3编程手册》
《STM32F1开发指南(精英版)-库函数版本_V1.0》(正点原子)
STM32官方数据手册

发布了1 篇原创文章 · 获赞 0 · 访问量 136

猜你喜欢

转载自blog.csdn.net/sunshine123S/article/details/104226302