STM32F103ZET6 最多可提供 5 路串口,有分数波特率发生器、支持同步单线通信和半双工单线通讯、支持 LIN、支持调制解调器操作、智能卡协议和 IrDA SIR ENDEC 规范、具有 DMA等。

最多可提供 6 路串口


一般的单片机硬件设计都会预留一个USART连接电脑,用于在调试程序时可以将调试信息打印在电脑端的串口调试助手。
但单片机的USART无法直接与PC通讯,需要借助CH340或CP2102等芯片实现USB与USART的相互转化。
STM32F103 板载的 USB 串口和 STM32F103ZET6 的串口是通过 P4 连接起来的

P4 是 PA9 和 PA10 的引出口。这里我们把 P4 的 RXD 和 TXD 用跳线帽与 PA9 和 PA10 连接起来。

TXD/RXD 是相对 CH340G 来说的,也就是 USB 串口的发送和接收引脚。

单片机的USART无法直接与232设备通讯,需要借助MAX232等芯片实现232与USART的相互转化。




关注第 5、6 位 RXNE 和 TC。








NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设 置 NVIC 中 断 分 组 2
串口1是挂载在APB2 下面的外设,所以使能函数为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA 时钟
当外设出现异常的时候可以通过复位设置,实现该外设的复位,然后重新配置这个外设达到让其重新工作的目的。
一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。复位的是在函数 USART_DeInit()中完成:
void USART_DeInit(USART_TypeDef* USARTx);//串口复位
比如我们要复位串口 1,方法为:
USART_DeInit(USART1); //复位串口 1
//GPIO 端口设置
GPIO_InitTypeDef GPIO_InitStructure;
接下来的两段代码就是将 TX(PA9)设置为推挽复用输出模式,将 RX(PA10)设置为浮空输入模式:
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0 发送端
//USART1_RX PA.10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10 接收端
中文参考手册:

usart.c里面包含了2个函数
一个是void USART1_IRQHandler(void);
另外一个是void uart_init(u32bound);
串口初始化是通过 USART_Init()函数实现的,
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
结构体指针变量 USART_InitStructure分析
typedef struct
{
uint32_t USART_BaudRate;
uint16_t USART_WordLength;
uint16_t USART_StopBits;
uint16_t USART_Parity;
uint16_t USART_Mode;
uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;
这个结构体有 6 个成员变量,所以我们有 6 个参数需要初始化。
USART_InitTypeDef USART_InitStructure;
//USART 初始化设置,接下来我们要设置串口 1 的初始化参数:
USART_InitStructure.USART_BaudRate = bound;//波特率设置;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8 位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl=
USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx |USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
如果需要开启中断的话。
使能串口中断的函数是:
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT,FunctionalState NewState)
函数的第二个入口参数标识使能串口的类型,也就是使能哪种中断。
比如在接收到数据的时候(RXNE 读数据寄存器非空)要产生中断,那么我们开启中断的方法是:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断,接收到数据中断
我们在发送数据结束的时候(TC,发送完成)要产生中断,那么方法是:
USART_ITConfig(USART1,USART_IT_TC,ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
紧接着,我们要进行 usart1 的中断初始化,设置抢占优先级值和子优先级的值:
//Usart1 NVIC 中断配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //对应中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //中断优先级配置
//完成串口中断优先级以及串口初始化之后,接下来就是开启串口中断以及使能串口了:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
串口使能是通过函数 USART_Cmd()来实现的,这个很容易理解,使用方法是:
USART_Cmd(USART1, ENABLE); //使能串口
void USART1_IRQHandler(void)函数是串口 1 的中断响应函数,当串口 1 发生了相应的中断后,就会跳到该函数执行。
EN_USART1_RX 和 USART_REC_LEN 都是在 usart.h 文件里面定义的
当需要使用串口接收的时候,我们只要在 usart.h 里面设置 EN_USART1_RX 为 1 就可以了。
不使用的时候,设置,EN_USART1_RX 为 0 即可,
默认是设置 EN_USART1_RX为 1,也就是开启串口接收的。
这里我们设计了一个小小的接收协议:
该函数,配合一个数组 USART_RX_BUF[],一个接收状态寄存器 USART_RX_STA(此寄存器其实就是一个全局变量,由作者自行添加。由于它起到类似寄存器的功能,这里暂且称之为寄存器)实现对串口数据的接收管理。
USART_RX_BUF 的大小由 USART_REC_LEN 定义,也就是一次接收的数据最大不能超过USART_REC_LEN 个字节。

当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中
同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数
当收到回车(回车的表示由 2个字节组成:0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待 0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。
如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收
等待该位被其他程序清除,从而开始下一次的接收。
如果迟迟没有收到 0X0D,那么在接收数据超过 USART_REC_LEN 的时候,会丢弃前面的数据,重新接收。
具体函数:
void USART1_IRQHandler(void) //串口 1 中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
//接收中断(接收到的数据必须是 0x0d 0x0a 结尾)
{
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成,后边清零器,第十六位是0则0,是1则1;
{
if(USART_RX_STA&0x4000)//接收到了 0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到 0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果 SYSTEM_SUPPORT_OS 为真,则需要支持 OS.
OSIntExit();
#endif
}
STM32 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。
当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。
STM32 库函数操作 USART_DR 寄存器发送数据的函数是:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
通过该函数向串口寄存器 USART_DR 写入一个数据。
STM32 库函数操作 USART_DR 寄存器读取串口接收到的数据的函数是:
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
通过该函数可以读取串口接受到的数据。
Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据
RS485发送和接收数据
串口的状态可以通过状态寄存器 USART_SR 读取。
状态寄存器 USART_SR
在固件库函数里面,读取串口状态的函数是:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
这个函数的第二个入口参数非常关键,它是标示我们要查看串口的哪种状态
例如我们要判断读寄存器是否非空(RXNE),操作库函数的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
我们要判断发送是否完成(TC),操作库函数的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_TC);
这些标识在库函数都有定义
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
#define USART_IT_IDLE ((uint16_t)0x0424)
#define USART_IT_LBD ((uint16_t)0x0846)
#define USART_IT_CTS ((uint16_t)0x096A)
#define USART_IT_ERR ((uint16_t)0x0060)
#define USART_IT_ORE ((uint16_t)0x0360)
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。
经常我们在中断处理函数中,要判断该中断是哪种中断,使用的函数是:
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
比如我们使能了串口发送完成中断,那么当中断发生了, 我们便可以在中断处理函数中调用这个函数来判断到底是否是串口发送完成中断,方法是:
USART_GetITStatus(USART1, USART_IT_TC)
返回值是 SET,说明是串口发送完成中断发生。
或者通过函数:
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
判断是否接受中断,如果是串口接受中断,则读取串口接受到的数据:
RS485发送和接收数据
回到 main.c,在 main.c 里面编写如下代码:
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
int main(void)
{
u8 t;
u8 len;
u16 times=0;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设 置 NVIC 中 断 分 组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
KEY_Init(); //初始化与按键连接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{ len=USART_RX_STA&0x3f; //得到此次接收到的数据长度
printf("\r\n 您发送的消息为:\r\n\r\n");
for(t=0;t<len;t++)
{ USART_SendData(USART1, USART_RX_BUF[t]); //向串口 1 发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
//等待发送结束
}
printf("\r\n\r\n"); //插入换行
USART_RX_STA=0;
}else
{ times++;
if(times%5000==0)
{ printf("\r\n 战舰 STM32 开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\n");
if(times%30==0)LED0=!LED0; //闪烁 LED,提示系统正在运行.
delay_ms(10);
}
}
}
为了使不同器件封装的外设 IO 功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上(remap)。
即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
简单的讲就是把管脚的外设功能映射到另一个管脚,但不是可以随便映射的,具体对应关系《STM32 中文参考手册 V10》
拿串口 1 为例来讲解。

从表中可以看出,默认情况下,串口 1 复用的时候的引脚位 PA9,PA10,同时我们可以将 TX 和 RX 重新映射到管脚 PB6 和 PB7 上面去。
重映射我们同样要使能复用功能的时候讲解的 2 个时钟外,还要使能 AFIO 功能时钟,然后调用重映射函数。
1)使能 GPIOB 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
2)使能串口 1 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
3)使能 AFIO 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
4)开启重映射:
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
USART1 只有一种重映射,而对于 USART3,存在部分重映射和完全重映射。所谓部分重映射就是部分管脚和默认的是一样的,而部分管脚是重新映射到其他管脚。而完全重映射就是所有管脚都重新映射到其他管脚。
