2. 多个串口使用

每一系类STM32单片机上都有多个串口,以野火指南者为例(STM32F103VET6)有5个串口,每个串口都是独立工作,比如可以用串口2、串口3与我们要控制的模块连接。

程序流程和调试信息由串口1发送,其他比如把串口3接收到的数据从串口1打印出来,调试助手发送给串口1的数据给其他串口等等这样的逻辑都基于先明确弄好每个串口各自的收发驱动程序部分。

下面以指南者 ESP8266 AT指令测试例程举例。

bsp_esp8266.c/.h bsp_usart.c/.h 主要部分对串口3和串口1外设选择适合的引脚分别初始化配置,使能了接收中断和空闲中断。

stm32f10x_it.c 编写的两个串口外设各自的中断函数。

stm32f10x_it.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 // 串口中断服务函数
 void DEBUG_USART_IRQHandler(void)
 {
 uint8_t ucCh;
     if ( USART_GetITStatus ( DEBUG_USARTx, USART_IT_RXNE ) != RESET )
     {
         ucCh  = USART_ReceiveData( DEBUG_USARTx );

         if ( strUSART_Fram_Record .InfBit .FramLength < ( RX_BUF_MAX_LEN - 1 ) )                       //预留1个字节写结束符
             strUSART_Fram_Record .Data_RX_BUF [ strUSART_Fram_Record .InfBit .FramLength ++ ]  = ucCh;

     }

     if ( USART_GetITStatus( DEBUG_USARTx, USART_IT_IDLE ) == SET )                                         //数据帧接收完毕
     {
     strUSART_Fram_Record .InfBit .FramFinishFlag = 1;

         ucCh = USART_ReceiveData( DEBUG_USARTx );                                                              //由软件序列清除中断标志位(先读USART_SR,然后读USART_DR)
 }
 }
 //ESP8266串口中断服务函数
 void macESP8266_USART_INT_FUN ( void )
 {
     uint8_t ucCh;
     if ( USART_GetITStatus ( macESP8266_USARTx, USART_IT_RXNE ) != RESET )
     {
         ucCh  = USART_ReceiveData( macESP8266_USARTx );

         if ( strEsp8266_Fram_Record .InfBit .FramLength < ( RX_BUF_MAX_LEN - 1 ) )                       //预留1个字节写结束符
             strEsp8266_Fram_Record .Data_RX_BUF [ strEsp8266_Fram_Record .InfBit .FramLength ++ ]  = ucCh;

     }

     if ( USART_GetITStatus( macESP8266_USARTx, USART_IT_IDLE ) == SET )                                         //数据帧接收完毕
     {
     strEsp8266_Fram_Record .InfBit .FramFinishFlag = 1;

         ucCh = USART_ReceiveData( macESP8266_USARTx );                                                              //由软件序列清除中断标志位(先读USART_SR,然后读USART_DR)
 }

 }

工程里给两个串口分别定义了一份可以保存接收字符串和其他标志的结构体。两个串口中断部分流程基本一致,在进入中断函数时,分别用库函数获取判断当前哪个使能了中断的标志位有触发。每接收一个字节都会进入一次中断,在判断USART_IT_RXNE标志后把接收的每一个字节都保存在结构体里面的一个字符数组, 当判断USART_IT_IDLE标志后可以认为一次接收结束,给结构体的中一个记录结束标志赋值。

在main函数主循环中不断轮询结构体中的记录接收结束标志,然后再执行编写的自定义对接收字符串处理过程,处理完后需要对结构体内容清空/复位。 例程的处理是直接把字符串从另一个串口发送出去,实现两个串口互相转发。

以上使用接收中断和空闲中断是一种方式,其他情况比如每次接收的字符个数是固定的或者末尾有确定固定的结束标志(比如回车符),可以在接收中断中每次判断累积接收个数或者字符本身再去赋值一个标志位给主循环或者其他函数处理。


其他应用的举例,比如工程里面配置了多个串口,想用其他串口外设号从printf函数输出,将原例程重定向fputc函数中USART_SendData函数第一个参数改为对应类型全局变量, 每次在调用printf前,先重新赋值该全局变量选择需要的串口。

fputc
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 int fputc(int ch, FILE *f)
 {
         /* 发送一个字节数据到串口 */
         USART_SendData(Select_DEBUG_USART, (uint8_t) ch);

         /* 等待发送完毕 */
         while (USART_GetFlagStatus(Select_DEBUG_USART, USART_FLAG_TXE) == RESET);

         return (ch);
 }

printf
1
2
3
4
5
6
7
USART_TypeDef* Select_DEBUG_USART;

//Select_DEBUG_USAR = USART1;
//Select_DEBUG_USAR = USART2;
Select_DEBUG_USAR = USART3;

 printf("欢迎使用野火STM32开发板\n\n");

更好的形式参考如下为每一个串口外设单独编写一个printf函数,需要哪个就用。

添加头文件#include <stdarg.h>,编写USART2的printf函数;

USART2_printf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void USART2_printf (char *s, ...) // 形参 “ ... ” 为可变参数。
 {
     char buffer[BUFFER_LEN+1];  // BUFFER_LEN 缓冲区长度
     u8 i = 0;

     va_list arg_ptr;
     va_start(arg_ptr, s);
     vsnprintf(buffer, BUFFER_LEN+1, s, arg_ptr);
     while ((i < BUFFER_LEN) && buffer)
     {
             USART_SendData(USART2, (u8) buffer[i++]);
             while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
     }
     va_end(arg_ptr);
 }