stm32串口
单片机的串口用的真的非常多,知识点也多,这里因为最近复习了一下做一个总结
这里的例子我都以103为例,407的话只是串口的配置不一样而已
USART初始化结构体
typedef struct
{
uint32_t USART_BaudRate; //波特率
uint16_t USART_WordLength; //字长
uint16_t USART_StopBits; //停止位
uint16_t USART_Parity; //校验控制
uint16_t USART_Mode; //模式选择 接收或者发送
uint16_t USART_HardwareFlowControl;//有无硬件流控制
} USART_InitTypeDef;
串口操作相关库函数
void USART_Init(); //串口初始化:波特率,数据字长,奇偶校验,硬件流控以及收发使能
void USART_Cmd();//使能串口
void USART_ITConfig();//使能相关中断
void USART_SendData();//发送数据到串口,DR
uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据
FlagStatus USART_GetFlagStatus();//获取状态标志位
void USART_ClearFlag();//清除状态标志位
ITStatus USART_GetITStatus();//获取中断状态标志位
void USART_ClearITPendingBit();//清除中断状态标志位
串口的一般配置步骤
串口时钟使能,GPIO时钟使能:RCC_APB2PeriphClockCmd();
串口复位:USART_DeInit(); 这一步不是必须的
GPIO端口模式设置:GPIO_Init();
串口参数初始化:USART_Init();
开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)
NVIC_Init();
USART_ITConfig();
使能串口:USART_Cmd();
编写中断处理函数:USARTx_IRQHandler();
串口数据收发:
void USART_SendData();//发送数据到串口,DR
uint16_t USART_ReceiveData();//接受数据,从DR读取接受到的数据
串口传输状态获取:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
下面以串口一为例
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//①串口时钟使能,GPIO 时钟使能,复用时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|
RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA 时钟
//②串口复位
USART_DeInit(USART1); //复位串口 1
//③GPIO 端口模式设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //ISART1_TX PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_RX PA.10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10
//④串口参数初始化
USART_InitStructure.USART_BaudRate = 115200; //波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为 8 位
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);
//⑤初始化 NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ; //抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //中断优先级初始化
//⑤开启中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断
//⑥使能串口
USART_Cmd(USART1, ENABLE); //使能串口
中断服务函数
下面我们来逐步分析
标志位
首先是一些重要的状态标志位
单片机发送的时候是先将数据发送到发送数据寄存器,然后在通过发送移位寄存器一位一位发送到TX引脚,接收的时候是通过rx发送到接收移位寄存器,然后再给接收数据寄存器,在给单片机
标志位 | 作用 |
---|---|
TXE | 发送数据寄存器空,发送单个字节使用 |
TC | 发送完成,发送多个字节时使用 |
TXIE | 发送完成中断使能 |
RXNE | 接收数据寄存器非空 |
RXNEIE | 接收缓冲区非空中断使能 |
注意:TXE和TC的复位值为1,而RXNE的复位值为0
这里需要理解清楚
发送数据寄存器
TXE为1:TDR里的数据全部到了移位寄存器,并且没有新的数据进TDR
TXE为0;TDR里有数据,非空,则TXE=0
发送移位寄存器
TC为1:从TDR过来的数据全部被移送到TX引脚,并且TDR里也没有新的数据
TC为0:从TDR过来的数据还没有完全被移过去,或者之前TDR里的数据被移走了,但TDR里又来了新的数据
来几个例子试一下,这里省略了串口的配置,也没有开启中断
USART_SendData(USART1,'d');
USART_SendData(USART1,'y');
USART_SendData(USART1,'k');
结果: k
最后串口助手上只打印了一个k
原因:把‘d’发送到发送数据寄存器的时候,因为没有停顿,所以接着发送的‘y’就把‘d’覆盖了,同理可知‘y’也被’k’覆盖了,最后发送到发送移位寄存器,和TX引脚
USART_SendData(USART1,'d');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
USART_SendData(USART1,'y');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
USART_SendData(USART1,'k');
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
结果:dyk
最后串口助手上只打印了dyk
第二个结果不一样是因为加了循环判断标志位,TDR里有数据,非空,则TXE=0,如果为0就会一直在循环里面,直到数据发送到发送移位寄存器,才结束循环
最后标志位全部改成TC
USART_SendData(USART1,'d');
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
USART_SendData(USART1,'y');
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
USART_SendData(USART1,'k');
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
结果:yk
最后串口助手上只打印了yk
可以看到TC是先读,再写入,然后就是前面强调的,TXE和TC的复位值为1
所以写完‘d’后直接就退出循环了,‘y’就把‘d’给覆盖了
发现状态标志不同,产生的结果也都不同
然后这里再提一下
USART_GetITStatus和USART_GetFlagStatus的区别
USART_GetITStatus
该函数不仅会判断标志位置1,同时还会判断是否使能了
相应的中断,所以在串口中断函数中,使用该函数
USART_GetFlagStatus
该函数只判断标志位
对发送函数的封装
发送一个字节
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t data)
{
USART_SendData(pUSARTx, data);
while( USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET );
}
发送一个数组
/* 发送8位数据的数组 */
void Usart_SendArray(USART_TypeDef* pUSARTx, uint8_t *array,uint8_t num)
{
uint8_t i;
for( i=0; i<num; i++ )
{
Usart_SendByte(pUSARTx, array[i]);
}
while( USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET );
}
发送字符串
/* 发送字符串 */
void Usart_SendStr(USART_TypeDef* pUSARTx, uint8_t *str)
{
uint8_t i=0;
do
{
Usart_SendByte(pUSARTx, *(str+i));
i++;
}while(*(str+i) != '\0');
while( USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET );
}
中断接收
u8 res;
if(USART_GetITStatus(USART1,USART_IT_RXNE))
{
res= USART_ReceiveData(USART1);
USART_SendData(USART1,res);
}
最后不要用while而用if
printf重定向
一定要把这个Use MicroLIB给勾上
否则就要 usart.h加入这段代码
//加入以下代码,支持 printf 函数,而不需要选择 use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
_sys_exit(int x)
{
x = x;
}
//重定义 fputc 函数
int fputc(int ch, FILE *f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
USART_SendData(USART1,(uint8_t)ch);
return ch;
}
///重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}