串口
1.通信:设备间信息的交互
有线通信:以太网,串口,USB,CAN等
无线通信:wifi,蓝牙,红外,2/3/4/5G,广播,NB-IOT等
2.通信的分类
并行通信:一次传输多位数据,传输速度快,多使用在近距离传输,CPU中的总线,MCU与内存,下载烧录器等
串行通信:一次传输一位数据。传输距离远
串行通信按照数据传输方向:
单工:数据单方向传输 -----广播,收音机
半双工:数据可以双向传输,同一时刻只能一个方向传输(A到B)-----对讲机
全双工:数据可以双向传输 ---手机,SPI等
同步通信:接收时钟与发送时钟严格同步,通常要有同步时钟
异步通信:字符与字符之间的传送是完全异步的,位与位之间的传送基本上是同步的
串行通信
串行通信接口(通常指COM接口),是采用串行方式扩展接口。
1.物理层
a.管脚
TXD数据发送管脚,RXD数据接收管脚,GND信号地
b.连接方式
直接相连,距离近,芯片与模块之间,模块与模块之间
2.RS232------负逻辑电平
3.数据链路层 --------------位协议 ----------232 协议
起始位 数据位 奇偶校验位 停止位
位数 1 5~8 1 1~2
电平 0 1/0 1/0 1
起始位:低电平 ------ 数据传输的开始
数据位:5~8位
奇偶校验位:用来判断传输的数据是否正确(奇偶校验是不准确的校验方式)
奇校验:数据位中1的个数+奇偶校验位中1的个数为奇数个。
偶校验:数据位中1的个数+奇偶校验位中1的个数为偶数个。
以奇校验为例:
发送数据 1010 0010
发送 1010 0010 0
接收 1010 0011 0 -------错误
1010 0001 0 -------正确(错误)
可以看出当传输的数据如果1变为0的个数和0变为1的个数凑巧都为奇数个或偶数个的时候,奇偶校验的结果是正确,可是实际数据却发生了错误,可见奇偶校验是不准确的。
停止位:高电平 --数据传输结束
位:CLK一个时钟周期
比如2位停止位就是2个时钟周期的高电平就产生停止接收数据的信号,而数据位8位就是8个时钟周期可以产生8bit的数据,即每一个时钟周期传送的数据被当作一个bit来解析。
常用的数据传输格式:1+8+0+1
波特率:1s传输多少数据位 例115200采用1+8+0+1的协议,即每10位有一个字节(8位 1byte = 8bit)的有效数据,就是一秒钟传输115200/10/1024 = 11.25kb的有效数据
而波特率是怎么计算的呢?
这里Fck是时钟频率
OVER8 = 0,OVER16 = 1
USARTDIV是我们要写入到BRR寄存器的值,
所以计算公式为Fck/brr/(16或8),这里16或8是通过过采样选择的
初始化串口
1、查看原理图确定引脚
注意:我们在途中看到的RXD,TXD是网络标号,可能会出错,但是引脚是不会出错的,比如图中RXD里面的功能写的是USART_TX,TXD写的功能是USART_RX,所以在使用时一定要根据引脚号来确定功能再进行配置。
2.看引脚是否有用作串口的功能
PA9,PA10,一个用作发送数据,一个用作接收数据,但是这两个引脚的通用功能都是作为IO口的,复用功能中才有串口的功能,所以在配置的时候要作为复用功能来使用。
3、配置串口
(1)开时钟,串口,GPIOA
(2)把复用功能映射到引脚上
高位寄存器控制(8~15)8个管脚,低位寄存器控制(0~7)
查看引脚复用映射来判断应该填入寄存器的值
使用串口功能,所以在AFRH9,10两部分写入AF7(0111)就将串口的功能复用到引脚上了。
注意:AFR寄存器配置的时候高低位是以数组来配置的,高位将GPIOA-AFR[]的下标写成1,低位写0.
(3)配置pa9,pa10的工作模式
pa9作为TX(发送)口所以要复用推挽输出
pa10作为RX(接受)配置为复用输入(这里只需配置为复用,无上下拉即可)
(4) 配置串口
串口一般要配置工作模式,通信协议,和波特率,这些都可以根据自己的需要进行配置。
USARTx_CR1寄存器需要配置的东西比较多,可以根据自己的选择进行配置,CR2寄存器目前我学到的只有一个关于通信协议的配置
在这一般会选择1个停止位。
波特率的配置:
在这里我们需要将波特率的效数部分和整数部分分别计算出来,然后写到USARTx_BRR这个寄存器中去,整数部分写在4~15位,小数部分要写入的值是通过公式计算(计算方法见上面)出来的值的效数部分乘16再写道BRR寄存器的低4位即可。
最后通过CR1寄存器使能串口,使能发送,使能接收即可使用串口进行通信了。
(5)回显函数的思想
用一个8位的变量接收从USARTx_DR(此时DR寄存器的值为通过串口接收的值)的值,再将变量的内容写入到USART_DR(如果向DR赋值,DR寄存器就是作为发送数据寄存器,如果取DR的值,则DR作为接收数据寄存器)中去,这样在串口助手中发送数据时就可以看到自己发送成功了没
(6)重定向printf函数
printf作为文件流的标准输出流,是将数据发送到标准输出设备(即屏幕)如果我们想要通过printf函数把我们想要输出的内容打印到串口上,我们只要重写fputc()函数即可,具体操作是,因为所有数据在计算机中都是以二进制存储的,所以每个字符都有对应的值,我们只要将fputc中c这个形参赋给DR寄存器就可以将printf的内容重新发送到串口上了。
这是fputc函数的原型 int fputc (int c, FILE *stream);,还有因为printf函数是在stdio.h这个头文件中的,但是我们的芯片不能放得下那么多内容,所以我们在使用时要勾选软件给我们提供的微库
这样就可以正常使用了,下面附上初始化代码。
这是寄存器配置(STM32F407)
#include "usart.h"
#include "stdio.h"
#include "string.h"
/*
函数名称 Usart1_Config
函数功能 初始化usart1
函数返回值 void
函数参数 u32 brr
*/
void Usart1_Config(u32 brr)
{
float div=0;
u32 div_m=0;//存放整数部分
u32 div_f=0;//存放小数部分
//开时钟 PA USART1
RCC->AHB1ENR |= (1<<0);
RCC->APB2ENR |= (1<<4);
//配置工作模式 p9 p10 复用模式做 usart1
GPIOA->AFR[1] &=~ (0xff<<4);
GPIOA->AFR[1] |= (0x7<<4);
GPIOA->AFR[1] |= (0x7<<8);
//PA9 复用推挽输出
GPIOA->MODER &=~ (3<<18);
GPIOA->MODER |= (2<<18);
GPIOA->OTYPER &=~ (1<<9);
GPIOA->OSPEEDR &=~ (3<<18);
GPIOA->OSPEEDR |= (2<<18);
GPIOA->PUPDR &=~ (3<<18);
//pa10
GPIOA->MODER &=~ (3<<20);
GPIOA->MODER |= (2<<20);
GPIOA->PUPDR &=~ (3<<20);
//配置串口
USART1->CR1 &=~ (1<<12);//字长1+8+n
USART1->CR1 &=~ (1<<10); //禁止奇偶校验
USART1->CR2 &=~ (3<<12); //停止位
//接收发送使能
USART1->CR1 |= (1<<2);
USART1->CR1 |= (1<<3);
//波特率
div = 84000000.0/16/brr;
div_m = (u32)div;
div_f = (div-div_m)*16;
USART1->BRR = (div_m<<4)|div_f;
//使能串口
USART1->CR1 |= (1<<13);
}
/*
函数名称 usart_Echo
函数功能 回显
函数返回值 无
函数参数 无
*/
void Usart1_Echo(void)
{
u8 data=0;
//接收数据
//判断是否接收到数据
while(!(USART1->SR & (1<<5)));
//保存数据
data = USART1->DR;
//发送数据
//判断上次数据是否发送完成
while(!(USART1->SR & (1<<6)));
USART1->DR = data;
}
u8 turn_led()
{
u8 data;
while(!(USART1->SR &(1<<5)));
data = USART1->DR;
while(!(USART1->SR & (1<<6)));
USART1->DR = data;
return data;
}
int fputc(int c, FILE * stream)
{
//判断上次数据是否发送完成
while(!(USART1->SR & (1<<6)));
USART1->DR = c;
return c;
}
这是通过库函数配置(STM32F103)
#include "usart.h"
#include "stdio.h"
void usart_Config(u32 brr)
{
GPIO_InitTypeDef pa9;
GPIO_InitTypeDef pa10;
USART_InitTypeDef usart1;
//pa9 tx pa10 rx usart1 开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//pa9 复用推挽输出
pa9.GPIO_Mode = (GPIO_Mode_AF_PP);
pa9.GPIO_Pin = (GPIO_Pin_9);
pa9.GPIO_Speed = (GPIO_Speed_50MHz);
GPIO_Init(GPIOA,&pa9);
//pa10 浮空输入
pa10.GPIO_Mode = (GPIO_Mode_IN_FLOATING);
pa10.GPIO_Pin = (GPIO_Pin_10);
GPIO_Init(GPIOA,&pa10);
//配置usart1
usart1.USART_BaudRate = brr;
usart1.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart1.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart1.USART_Parity = USART_Parity_No;
usart1.USART_StopBits = USART_StopBits_1;
usart1.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&usart1);
USART_Cmd(USART1,ENABLE);
}
void echo(void)
{
u8 data;
while((USART1->SR & (1<<5)) == 0);
data = USART1->DR;
while((USART1->SR & (1<<6)) == 0);
USART1->DR = data;
}
int fputc(int c,FILE * stream)
{
while((USART1->SR & (1<<6)) == 0);
USART1->DR = c;
return c;
}
来源:oschina
链接:https://my.oschina.net/u/4400455/blog/3442255