以下以STM32F407ZGT6为例
串口
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; }