1.什么是usart?
(b站up主江协科技给出以下解释)
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。
简单双向串口通信有两根通信线(发送端TX和接收端RX) TX与RX要交叉连接 当只需单向的数据传输时,可以只接一根通信线 当电平标准不一致时,需要加电平转换芯片
理论原理:
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
自带波特率发生器,最高达4.5Mbits/s
可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2) 可选校验位(无校验/奇校验/偶校验)
支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
2.提取关键点
串口基本点:他是全双工,点对点异步单端通信,因此必须供地(单端),约定波特率(异步)(双方时钟必须精确是前提,如果代码没问题收到的还是乱码,可能是硬件晶振有问题)。
常采用ttl电平:了解串口的逻辑电平定义。通常,0V表示逻辑低电平(0),而高电平(1)可以是3.3V或5V,具体取决于您的设备。
关键点:空闲时电平为高电平,起始位为低电平,停止位是高电平
难点:一般会避开难点,使用8位数据位无校验,一位停止位;数据位(8位)和 校验位(可选)这两位统称数据帧,数据帧如果可以选择九位,不使用校验位的话,就是9位数据了,这该怎么去使用。
3.为什么要使用串口?
因为它可以根据寄存器的一个字节数据,自动生成数据帧时序,就不用手动反转gpio 8次或9次了。说白了就是为了使用
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
//uint16_t Data 可不是8位,但我之会用八位的,不过跳转内部看会把高八位清除了(串口先发的低位,移位寄存器移八位,高八位清除也没关系)
(CRC检验以后值得多看看)
4.值得注意的细节问题
1.想使用printf()必须勾选microLIB
2. 时钟必须给正确,通常连接一个8mhz的外部晶振容易忽略。
如果是12mhz怎么改:
Ⅰ.stm32f10x.h里修改默认时钟
Ⅱ.system_stm32f10x.c里修改锁相环(应该也叫倍频器吧)
5.代码
(使用8位数据位无校验,一位停止位)
serial.h
#ifndef _SERIAL_H_
#define _SERIAL_H_
#include "stm32f10x.h"
//pa9是usart1的tx pa10是usart1的rx
//#define TX GPIO_Pin_9
//#define RX GPIO_Pin_10
#define RxPacket_MAX 1024
typedef struct{
uint8_t RXFlag;
uint8_t RxCont;
uint8_t RxPacket[RxPacket_MAX];
}SERIAL_DATA;
extern SERIAL_DATA serialData;
void serial_init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array,uint16_t length);
void Serial_SendString(char* String);
uint8_t Serial_GetRXFlag();
#endif
serial.c
#include "serial.h"
#include "stdio.h"
SERIAL_DATA serialData
={
.RXFlag=0,
.RxCont=0,
// .RxPacket[RxPacket_MAX]={'\0'},
};
//USART1 配置 PA9 -Tx PA10 -RX
void serial_init(void)
{
//配置IO口
GPIO_InitTypeDef GPIO_InitStruct={0};
USART_InitTypeDef USART_InitStructure={0};
NVIC_InitTypeDef NVIC_InitStructure={0};
//1,开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//2,配置IO口模式
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;//输出管脚
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//速度
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;//输入管脚
GPIO_Init(GPIOA,&GPIO_InitStruct);
//配置串口
//1,开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
//2,串口初始化参数
USART_InitStructure.USART_BaudRate=115200;//波特率
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//不使用硬件数据流控制
USART_InitStructure.USART_Mode=USART_Mode_Rx | USART_Mode_Tx;//同时开启发送和接收模式
USART_InitStructure.USART_Parity=USART_Parity_No;//不适用校验
USART_InitStructure.USART_StopBits=USART_StopBits_1;//1位停止位
USART_InitStructure.USART_WordLength=USART_WordLength_8b;//1次传输8位数据
USART_Init(USART1, &USART_InitStructure);
//3,串口使能操作
USART_Cmd(USART1, ENABLE);
//中断配置
//开启串口的接收中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void USART1_IRQHandler(){
if(USART_GetFlagStatus(USART1,USART_IT_RXNE)==SET){
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
uint8_t rxDataTem=USART_ReceiveData(USART1);
serialData.RxPacket[serialData.RxCont]=rxDataTem;
serialData.RxCont++;
serialData.RxCont %= RxPacket_MAX;//防止数组越界
}
//空闲中断
if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET)
{
serialData.RXFlag=1;
serialData.RxCont=0;
//空闲中断的清除
uint8_t data = USART1->DR;
data = USART1->SR;
}
}
uint8_t Serial_GetRXFlag(){
if(serialData.RXFlag==1){
serialData.RXFlag=0;
return 1;
}
return 0;
}
//串口发送 USART->DR = data
void Serial_SendByte(uint8_t Byte)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
USART_SendData(USART1, Byte);//发送一个字节
}
void Serial_SendArray(uint8_t *Array,uint16_t length){
uint8_t i;
for(i=0;i<length;i++){
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char* String){
uint8_t i;
for(i=0;String[i]!=0;i++){
Serial_SendByte(String[i]);
}
}
int fputc(int ch ,FILE *f){
Serial_SendByte(ch);
return ch;
}
6.理解函数
1.空闲中断什么时候会进?
空闲中断又叫帧中断,有一个字符(一个字节)的时间间隔就会进空闲中断,理论上(9600波特率)应该是1/9600 *8s没有数据就会进空闲中断。有博主说是超过两个字节的时间没有数据才会进空闲中断。https://blog.csdn.net/komtao520/article/details/112678287