STM32学习——串口
USART通用同步异步收发器(Universal Synchronous Asynchronous Receiver Transmitter)是一串行通信设备,可以灵活地与外部设备进行进行**全双工信息交换**
UART(Universal Asynchronous Receiver Transmitter),它是在 USART 的基础上裁剪了同步通信功能,只保留异步通信功能我们平时使用的串口通信都是 UART
1.通信接口背景知识介绍
1.1通信双方的两种通信方式
-
串行通信
- 传输原理:数据按位顺序传输
- 优点:占用引脚资源少
- 缺点:速度相对较慢
-
并行通信
-
传输原理:数据各个位同时传输 -
优点:速度快 -
缺点:占用引脚资源多
1.2串行通信的分类
单工:数据只支持在一个方向上传输(看电视)
**半双工:**同一时刻只允许在一个方向传输(对讲机)
全双工:允许同时在两个方向上传输(电话)
1.3常见的串行通信接口
2.USART功能概述
任何 UART 双向通信至少需要 3 个引脚,数据发送引脚 TXD,数据接收引脚 RXD,数
据参考地 GND。串口连接必须共地!!!
这里解释下电平标准,根据使用使用的电平标准不同,可以分为 TTL 和 RS232 标准,因为控制器一般都是 TTL 标准,因此如果需要进行 RS232 通信时,一定要使用 R232转换器进行 TTL 和 RS232 的电平转换
3.STM32串口资源
- 全双工异步通信。
- 分数波特率发生器系统,提供精确的波特率。发送和接收共用的可编程波特率,最高可达4.5Mbits/s
- 可编程的数据字长度(8位或者9位);
- 可配置的停止位(支持1或者2位停止位);
- 可配置的使用DMA多缓冲器通信。
- 单独的发送器和接收器使能位。
- 检测标志:① 接受缓冲器 ②发送缓冲器空 ③传输结束标志
- 多个带标志的中断源。触发中断。
- 其他:校验控制,四个错误检测标志。
4.串口通信过程
5.STM32串口通信使用方法
串口通信需要定义的参数
- 起始位
- 数据位(8位/9位)
- 奇偶校验位
- 停止位
- 波特率设置(收发两端要一致)
6.串口通信相关函数
- 初始化函数 bsp_InitUart
- 第1步: 配置GPIO
- 打开 GPIO 时钟
- 打开 UART 时钟
- 配置 USART Tx 为复用功能
- 配置 USART Rx 为复用功能
- 第2步: 配置串口硬件参数
- 第3步: Usart1 NVIC 配置
- 第4步: 使能串口1
- 中断服务函数 USART1_IRQHandler
- 状态清零函数 Uart0_STA_Clr
- 打印输出函数 fput
- 数据发送函数 USART1_Send_Data
7.STM32F1的 USART 库
7.1对应库函数
-
使能 USART 时钟 下面的函数用于使能 USART1 的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USARTx,ENABLE) 下面的函数用于使能 USART2、USART3 UART4、UART5 RCC_APB1PeriphClockCmd(RCC_APB1Periph_UARTx,ENABLE) -
使能 GPIO APB2 时钟(GPIO 是挂载在 APB2 高速总线上的) -
用函数 GPIO_Init()配置 GPIO 引脚,主要有以下两种配置方式,分别对应 RXD 和 TXD的引脚 ? 输入:浮空,上拉 ? 复用输出:复用推挽 -
外设复用功能配置外设的复用功能
7.2初始化和配置函数
下面是串口 1 的初始化函数,主要完成串口的设置。
void bsp_InitUart1(uint32_t baud)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
#if EN_USART1_RX
NVIC_InitTypeDef NVIC_InitStructure;
#endif
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
USART_InitStructure.USART_BaudRate=baud;
USART_InitStructure.USART_WordLength=USART_WordLength_8b;
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);
#if EN_USART1_RX
NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
#endif
USART_Cmd(USART1,ENABLE);
}
主要完成串口波特率,数据长度,校验位,流控,模式的设置,在这个函数里没有对GPIO 引脚做宏定义,是因为 STM32F1 系列的串口 1 引脚序号都是一样的,这样本身就便于在移植时不做任何 GPIO 定义的更改。
下面是中断服务函数 USART1_IRQHandler,这里主要使用空闲中断,当接收到一帧数据后,总线空闲中断后,使用读取 USART1->SR 的方式再读取 DR 的方式来清除 IDLE 标记,同时将 ReceiveState 标志置 1。
void USART1_IRQHandler(void)
{
uint8_t Res=Res;
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
{
USART_RX_BUF[RxCounter++]=USART1->DR;
}
if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET)
{
Res=USART1->SR;
Res=USART1->DR;
ReceiveState=1;
}
}
下面是状态清零函数,这个函数比较简单,函数只是简单的将 RxCounter 和 ReceiveState 两个全局变量清零的操作做了一个封装,方便外部函数进行调用。
void Uart0_STA_Clr(void)
{
RxCounter=0;
ReceiveState=0;
}
有过 C 语言基础的用户,都知道一个格式化打印输出的函数 printf。在 STM32 里我们要使用 printf 的方法,必须做一些操作,这里给出了一种方法,通过代码的方式实现printf,而不需要使用 MicroLIB。注意,要使用下面的文件,有些关键词是在 stdio.h 里申明的,我们在 bsp.h 里已经做了包含。
#if 1
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);
USART1->DR = (u8) ch;
return ch;
}
#endif
这里解释一下 0x40,0x40 是 USART_FLAG_TC 的标志。(USART1->SR&0X40)==0 这句和 USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET 这句功能是一样的,一种是寄存器的写法,一种是使用库函数的方法。而实际在底层都是最终操作的寄存器。
使用库函数进行数据发送的函数如下,这个函数主要是便于发送 16 进制数据的。
void USART1_Send_Data(uint8_t *buf,uint8_t len)
{
uint8_t t;
for(t=0;t<len;t++)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
USART_SendData(USART1,buf[t]);
}
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
}
另外介绍一下 bsp_uart.h 里的内容,特别注意一下 extern 关键词。
#ifndef __BSP_UART_H
#define __BSP_UART_H
#include "sys.h"
#define USART_REC_LEN 1024
#define EN_USART1_RX 1
extern uint8_t USART_RX_BUF[USART_REC_LEN];
extern uint8_t ReceiveState;
extern uint16_t RxCounter;
void bsp_InitUart1(uint32_t baud);
#if EN_USART1_RX
void Uart0_STA_Clr(void);
#endif
#endif
如果某个 C 文件中定义的全局变量,需要被其它 C 文件使用时,函数实体定义在 C 文件里,而在.h 头文件里用 extern 申明该变量,且在申明时不能对该变量赋值,其它 C 文件调用时用 include 包含该头文件即可。
7.3主函数
#include "bsp.h"
int main(void)
{
uint8_t Key_Code;
uint16_t t;
uint16_t len;
bsp_Init();
printf("DS0 正在闪烁(闪烁频率 = 1Hz)\r\n");
printf("操作按键会打印按键事件\r\n");
bsp_StartAutoTimer(0, 500);
while(1)
{
bsp_Idle();
if(bsp_CheckTimer(0))
{
bsp_LedToggle(1);
printf("请输入数据,以回车键结束\r\n");
}
Key_Code=bsp_GetKey();
if(Key_Code!=KEY_NONE)
{
switch(Key_Code)
{
case WKUP_DOWN:
bsp_LedOn(1);
printf("WKUP键按下\r\n");
break;
case WKUP_UP:
printf("WKUP键抬起\r\n");
break;
case KEY0_DOWN:
printf("KEY0键按下\r\n");
break;
case KEY0_UP:
printf("KEY0键抬起\r\n");
break;
default :
break;
}
}
if(ReceiveState)
{
len=RxCounter;
printf("\r\n发送的消息为\r\n\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1,USART_RX_BUF[t]);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
}
printf("\r\n\r\n");
Uart0_STA_Clr();
}
}
}
|