一文学会标准库USART
USART全解
提示:本文使用开发板是STM32f103vet6,本文使用代码承接上一张
`
文章目录
- USART全解
- 题目重述
- 1. USART模块简介
- 4,USART 通信
- 2. USART的基本用法
- 3. 移位寄存器和串并转换
- 4. 数据帧格式的设置方法
- 5. 波特率的设置方法
- 6. 编程接口
- 7,USART 框图
- 8,USART 常用 API
- 9,软件设计流程
- 10,代码模块(usart串口输入)
- 11,代码模块(usart数据处理)
- 12,开发板链接
题目重述
请根据提供的目录内容,逐步简单解释如何理解串口(USART)的使用,包括发送和接收数据,以及奇校验和偶校验的设置。
1. USART模块简介
4,USART 通信
USART 通用同步、异步收发。如果芯片支持时钟同步,则称 USART,如果没有就是 UART。
UART 包含 TxD 发送线,RxD 接收线两根数据线,TxD 与 RxD 交叉连接。全双工、异步、串行通信。
一帧包含:起始位 + 数据位 + 奇偶校验位 + 停止位。
起始位:信号由高变到低并且维持一个位的低电平,称为起始位。
数据位:LSB 低位先出,0 低电平,1 高电平,逐位发送。有 8 位和 9 位。
奇偶校验位:用于检验数据是否出错。分奇校验、偶校验、无校验。
奇校验是指数据位中 1 的个数如果是奇数个,则为 0,否则为 1;
偶校验是指数据位中 1 的个数如果是偶数个,则为 0,否则为 1。
无校验即不使用校验位。
停止位:数据位和奇偶校验位结束后,总线变为高电平,并且维持 1 位、1.5 位、2 位等位数的高电平。
空闲状态:总线处于高电平定义为空闲状态。
波特率:每秒传输的位数,单位 bps。常见的 9600,115200。
硬件流控:
接收方的 nRTS 引脚与发送方的 nCTS 连接,当接收方将 nRTS 拉高电平时,发送方检测到 nCTS 高电平就暂停发送;
当接收方将 nRTS 拉低电平,发送方检测到 nCTS 低电平就继续发送。
这样即可实现总线上数据流量的控制。流控是由接收方控制的。
串口的配置:
2. USART的基本用法
要使用串口通信,需配置以下内容:
- 设置数据传输的波特率(如9600、115200,表示每秒传多少位)
- 设定数据长度(通常是8位)
- 选择是否启用校验位(奇校验或偶校验)
- 设置停止位个数(1或2)
发送和接收通过TX(发送引脚)和RX(接收引脚)完成。
3. 移位寄存器和串并转换
- 并行数据:多个位同时传输(如8根线一起传)
- 串行数据:一位一位传(只需1根线)
移位寄存器负责转换:
- 发送时:把并行数据(如一个字节)→ 一位位移出(串行)
- 接收时:把收到的串行数据 → 汇集成一个字节(并行)
4. 数据帧格式的设置方法
数据帧是一次传输的基本单位,结构如下:
起始位 | 数据位(8位) | 校验位(可选) | 停止位 |
---|---|---|---|
1位 | D0~D7 | 1位 | 1~2位 |
设置示例:8-N-1
表示 8位数据、无校验、1位停止位
若为8-E-1
:表示偶校验;8-O-1
:奇校验
5. 波特率的设置方法
波特率 = 每秒传输的位数(如115200 bit/s)
-
双方设备必须设置相同波特率才能正确通信
-
单片机通过内部定时器或波特率发生器生成该时钟
-
计算公式:
分频值=系统时钟16×波特率\text{分频值} = \frac{\text{系统时钟}}{16 \times \text{波特率}} 分频值=16×波特率系统时钟
6. 编程接口
这是软件如何控制USART的方式:
- 初始化配置(波特率、数据位、校验位等)
- 发送函数:将数据写入发送寄存器,硬件自动发送
- 接收函数:从接收寄存器读取收到的数据
- 中断或轮询方式处理数据
7,USART 框图
数据从 TDR 移入 发送移位寄存器后,硬件就将 TXE 标志置 1,此时并不表示数据发送完成,只表示 TDR 空了,可以发送下一个数据了。
什么时候才发送完成?当停止位发送完成后,TC 标志置 1。TC 标志表示数据发送完成。
当接收移位寄存器中收满数据后,硬件自动将数据移入 RDR 寄存器,并将 RXNE 标志置 1,表示接收到了数据。
当一帧数据传输完毕,没有下一帧数据要继续发送,此时总线处于空闲状态,产生一次空闲标志 IDLE。之后持续空闲,不会重复产生空闲标志。除非下一次传输完毕空闲,才会再次产生空闲标志。
8,USART 常用 API
// 初始化
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
// 使能/禁用
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);// DMA 使能/禁用
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);// 发送、接收数据
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);// 中断配置
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
// 获取/清除状态
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
9,软件设计流程
10,代码模块(usart串口输入)
//main.c#include "stm32f10x.h"
#include <stdio.h>
#include "gpio.h"
#include "delay.h"
#include "exti.h"
#include "usart.h"uint8_t SendData[] = {"指针跳跃!\r\n"};int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);MX_GPIO_Init();delay_Init();MX_EXTIO_Init( 3, 3);MX_USART1_Init(115200);MX_USART1_NVIC_Init(2,0);for(uint16_t i=0;i<sizeof(SendData);i++){USART_ClearFlag(USART1, USART_FLAG_TC); //清楚发送完成标志位USART_SendData(USART1,SendData[i]); //按位发送while(USART_GetFlagStatus( USART1, USART_FLAG_TC ) == RESET){} //等待发送}while(1)
{GPIO_WriteBit(GPIOC,GPIO_Pin_6 ,Bit_RESET);//输出低电平delay_ms(300); GPIO_WriteBit(GPIOC,GPIO_Pin_6 ,Bit_SET); //输出高电平delay_ms(300); }}
//usart.h
#ifndef _USART_H_
#define _USART_H_#include "stm32f10x.h"
#include <stdio.h>void MX_USART1_Init(uint32_t Baud);
void MX_USART1_NVIC_Init(uint8_t Preemption,uint8_t Sub);#endif
//usart.c
#include "usart.h"void MX_USART1_Init(uint32_t Baud)
{
//-------------------------------------GPIO Init-------------------------------------------//GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE); //使能時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 ,ENABLE); //使能時鐘//PA0 TXGPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //輸出复用推挽GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; //選擇PINGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);//PA10 RXGPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //上拉模式GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; //選擇PINGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);USART_Cmd(USART1,ENABLE); //使用串口//-------------------------------------USART Init-------------------------------------------//USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_BaudRate = Baud; //波特率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);USART_Cmd( USART1, ENABLE);}void MX_USART1_NVIC_Init(uint8_t Preemption,uint8_t Sub)
{//-------------------------------------NVIC Init-------------------------------------------//NVIC_InitTypeDef NVIC_InitStruct;USART_ITConfig( USART1, USART_IT_RXNE ,ENABLE);USART_ITConfig( USART1, USART_IT_RXNE, ENABLE);NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //外部中断线0NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = Preemption; //抢占优先级NVIC_InitStruct.NVIC_IRQChannelSubPriority = Sub; //响应优先级NVIC_Init(& NVIC_InitStruct);}void USART1_IRQHandler(void)
{uint8_t res;if (USART_GetITStatus( USART1,USART_IT_RXNE) != RESET){res = USART_ReceiveData(USART1);USART_SendData(USART1,res); //按下发送}}
11,代码模块(usart数据处理)
//main.c
#include "stm32f10x.h"
#include <stdio.h>#include "gpio.h"
#include "delay.h"
#include "exti.h"
#include "usart.h"uint8_t SendData[] = {"指针跳跃!\r\n"};int main(void)
{uint32_t Led_ts = 0;BitAction BitVal = Bit_SET;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);MX_GPIO_Init();delay_Init();MX_EXTIO_Init( 3, 3);MX_USART1_Init(115200);MX_USART1_NVIC_Init(2,0);// for(uint16_t i=0;i<sizeof(SendData);i++)
// {
// USART_ClearFlag(USART1, USART_FLAG_TC); //清楚发送完成标志位
// USART_SendData(USART1,SendData[i]); //按位发送
// while(USART_GetFlagStatus( USART1, USART_FLAG_TC ) == RESET){} //等待发送
// }printf("%s",SendData);while(1)
{if(U1RX_IDLE_FLAG == 1 ){printf("Recv[%d];%s",U1RX_LEN,U1RX_DATA);if(strncmp(U1RX_DATA,"启动蜂鸣器",10) == 0){GPIO_WriteBit(GPIOC ,GPIO_Pin_9, Bit_RESET);}else if(strncmp(U1RX_DATA,"关闭",4) == 0){GPIO_WriteBit(GPIOC ,GPIO_Pin_9, Bit_SET);}if(strncmp(U1RX_DATA,"启动绿灯",8) == 0){GPIO_WriteBit(GPIOC ,GPIO_Pin_7 , Bit_RESET);}else if(strncmp(U1RX_DATA,"关绿灯",6) == 0){GPIO_WriteBit(GPIOC ,GPIO_Pin_7, Bit_SET);}if(strncmp(U1RX_DATA,"启动红灯",8) == 0){GPIO_WriteBit(GPIOC ,GPIO_Pin_8 , Bit_RESET);}else if(strncmp(U1RX_DATA,"关红灯",6) == 0){GPIO_WriteBit(GPIOC ,GPIO_Pin_8, Bit_SET);}memset(U1RX_DATA,0,U1RX_MAX_LEN);U1RX_IDLE_FLAG = 0;U1RX_LEN = 0;}if((GetTick() - Led_ts) > 1000){BitVal = (BitAction)! BitVal;GPIO_WriteBit(GPIOC,GPIO_Pin_6 , BitVal);Led_ts = GetTick();}}}
//usart.h
#ifndef _USART_H_
#define _USART_H_#include "stm32f10x.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>void MX_USART1_Init(uint32_t Baud);
void MX_USART1_NVIC_Init(uint8_t Preemption,uint8_t Sub);#define U1RX_MAX_LEN 128extern char U1RX_DATA[ U1RX_MAX_LEN ];
extern uint8_t U1RX_LEN;
extern bool U1RX_IDLE_FLAG ;#endif
//usart.c
#include "usart.h"char U1RX_DATA[ U1RX_MAX_LEN] = { 0 };
uint8_t U1RX_LEN;
bool U1RX_IDLE_FLAG = 0;void MX_USART1_Init(uint32_t Baud)
{
//-------------------------------------GPIO Init-------------------------------------------//GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE); //使能時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 ,ENABLE); //使能時鐘//PA0 TXGPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //輸出复用推挽GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; //選擇PINGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);//PA10 RXGPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; //上拉模式GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; //選擇PINGPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);USART_Cmd(USART1,ENABLE); //使用串口//-------------------------------------USART Init-------------------------------------------//USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_BaudRate = Baud; //波特率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);USART_Cmd( USART1, ENABLE); //闭合总开关}void MX_USART1_NVIC_Init(uint8_t Preemption,uint8_t Sub)
{//-------------------------------------NVIC Init-------------------------------------------//USART_ITConfig( USART1, USART_IT_RXNE ,ENABLE);//配置中断RXNEUSART_ITConfig( USART1, USART_IT_IDLE ,ENABLE); //配置中断IDLE//配置中断USART_ITConfig( USART1, USART_IT_RXNE, ENABLE);//配置NVICNVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //外部中断线0NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能中断NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = Preemption; //抢占优先级NVIC_InitStruct.NVIC_IRQChannelSubPriority = Sub; //响应优先级NVIC_Init(& NVIC_InitStruct);}void USART1_IRQHandler(void)
{if (USART_GetITStatus( USART1,USART_IT_RXNE) != RESET){uint8_t res= USART_ReceiveData(USART1);if( U1RX_LEN <U1RX_MAX_LEN){U1RX_DATA [U1RX_LEN++] =USART_ReceiveData(USART1);}else{U1RX_IDLE_FLAG = 1; //超过最大接受长度。强制处理接收数据}}if(USART_GetITStatus( USART1,USART_IT_IDLE) != RESET){USART1 ->SR; //先读 SR 清楚IDEL中断标志位USART1->DR; //再度DR 清楚IDEL中断标志位U1RX_IDLE_FLAG = 1; //置位空闲中断接受标志位,以处理数据}
}//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{ int handle;
}; FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{ x = x; } //重定义fputc函数
int fputc(int ch, FILE *f)
{ while((USART1->SR&0X40)==0);//循环发送,直到发送完毕 USART1->DR = (u8) ch; return ch;
}
#endif
12,开发板链接
本文章所使用开发板链接:https://pointerleap.taobao.com/