STM32F407 通用同步异步收发器(串口)

概述


  如上图所示,通过stm32f4xx技术参考手册,可知stm32f407有6个串口,4个USART,2个UART。其中各个模式表示的解释如下:
    1.异步模式:双方约定一个时钟频率进行发送和接收,发送端可以在任意时刻开始发送字符,因此必须在每一个字符的开始和结束的地方加上标志,即加上开始位和停止位,以便使接收端能够正确地将每一个字符接收下来,当然接收端需要提前做好接收准备;
    2.硬件流控制:通过引脚nCTS、nRTS通过这两个引脚进行接收控制,防止数据接收太快导致缓冲区溢出,造成数据丢失或者需要先对数据进行处理。这两种情况都可以将nRTS引脚降为-5V停止发送数据,当需要继续接收数据时将nCTS拉为高电平, 接收的数据流就恢复了;
    3.多缓冲区通信(DMA):接收和发送数据都不占用CPU,而是提前设置好DMA的发送地址和接收地址,当有数据存在时DMA控制器便会自动发送或接收;
    4.多处理器通信:设定一个USART为主USART其他的USART为从USART,从USART的RX输入与主USART的TX输出相连接,其各自的TX输出在逻辑上通过与运算连在一起,类型于局域网,每个从USART都需要设置一个地址,主USART通过地址与指定的USART进行通信;
    5.同步:发送和接收数据通过时钟输出引脚的时钟频率来进行采样,发送方和接收方的数据保持一致;
    6.智能卡:支持符合ISO 7816-3标准中定义的异步协议智能卡;
    7.半双工:仅使用TX引脚进行双向通信;
    8.IrDA:需要在TX、RX线上加上红外编码解码器,进行选择;
    9.lIN:局域网互联模式。

接口标准

  串口的接口标准有RS232、RS485、RS422这是外部通信接口,还有一个TTL电平标准因为驱动能力和抗干扰能力很差所以不适用于外部通信,以下是它们的区别:

RS232 RS422 RS485 TTL
接口结构 标准为9针或者25针 无具体标准,一般使用9针 无具体标准,一般使用9针 无具体标准,根据实际情况选择使用的数目
电气特性 -15v<"1"<-3v,15v>"0">3v AB脚电压差决定 AB脚电压差决定 +5V等价于逻辑1,0V等价于逻辑0
传输距离 最大为15米 最大为1200米 最大为1200米 不建议超过1米
多点通信 不支持多站接收 最多可连接10个接收器 运行多达128个收发器 不同串口的RX和TX相互连接,形成环路
通信电缆 三芯双绞线/屏蔽线等 用两对特性阻抗为120Ω的双绞屏蔽电缆 两芯双绞线/屏蔽线等
传输速率 最高19200bps 最高1Mbps 最高10Mbps 最高230400bps
  上图为RS232、RS422、RS485,9针公头的接口定义,仅供参考,需要查看具体设备的说明文档进行确认。接收接发送,正接正,负接负。

帧格式


  如上图所示,一个字符帧由起始位和数据位、奇/偶校验(可选)和停止位组成,数据位从最低位开始传输。

实验程序

  编写一个串口实验程序,功能很简单,接收到指定的16进制数便打开或关闭相应的LED灯。相关宏定义和工程模板参考我之前的环境搭建随笔和实验程序;
usart.c

#include <stdio.h>
#include "stm32f4xx.h"
#include "common.h"
#include "usart.h"

/*
***出现printf、scanf等文件操作,因程序中并没有对这些函数的底层实现,
使得设备运行时会进入软件中断“BAEB  BKPT 0xAB”处,执行次完成后程序将停止。
当使用pragma向编译器声明了__use_no_semihosting_swi,便不会使程序遇到
这些文件操作函数时不停在此中断处
*/
#pragma import(__use_no_semihosting_swi) 

struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;

/* 重定向printf函数 */
int fputc(int ch,FILE *f)
{
	//发送数据
	USART_SendData(USART1,ch);
	
	//检查是否发送完成
	while(RESET==USART_GetFlagStatus(USART1,USART_FLAG_TXE));	
	
	return ch;
}	

/* 重定向scanf函数 */
int getc(FILE *f)
{
	/* 等待串口输入数据 */
	/* 有了这个等待就不需要在中断中进行了 */
	while(USART_GetFlagStatus(USART1 , USART_FLAG_RXNE)==RESET);
	/* 返回值进行强制类型转换 */
	return (int)USART_ReceiveData(USART1);
}


void _sys_exit(int return_code) {
label: goto label; /* endless loop */
}

void usart1_init(uint32_t baud)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        EXTI_InitTypeDef EXTI_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        USART_InitTypeDef USART_InitStructure;
	
	//配置PA9和PA10引脚,为AF模式(复用功能模式)
	GPIO_InitStructure.GPIO_Pin  = USART1_TX_PIN|USART1_RX_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF ;//配置为复用功能模式
	GPIO_InitStructure.GPIO_Speed =GPIO_Speed_100MHz ;//配置引脚的响应时间=1/100MHz .
	//从高电平切换到低电平1/100MHz,速度越快,功耗会越高
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP ;//推挽的输出模式,增加输出电流和灌电流的能力
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL ;//不使能内部上下拉电阻
	GPIO_Init(USART1_PORT,&GPIO_InitStructure);

	//将PA9和PA10的功能进行指定为串口1
	GPIO_PinAFConfig(USART1_PORT,USART1_TXSource,GPIO_AF_USART1);
	GPIO_PinAFConfig(USART1_PORT,USART1_RXSource,GPIO_AF_USART1);	
	
	//配置串口1的参数:波特率、数据位、校验位、停止位、流控制
	USART_InitStructure.USART_BaudRate = baud;//波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//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);
	
	//使能串口1的接收中断
	USART_ITConfig(USART1,USART_IT_RXNE,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);
	
	//使能串口1工作
	USART_Cmd(USART1, ENABLE);
}

void USART1_IRQHandler(void)
{
	uint8_t d;
	
	//检测是否接收到数据
	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
	{
		d = USART_ReceiveData(USART1);

		if(d == 0X01)LED1ON;
		if(d == 0XF1)LED1OFF;	

		if(d == 0X02)LED2ON;
		if(d == 0XF2)LED2OFF;
		
		if(d == 0X03)LED3ON;
		if(d == 0XF3)LED3OFF;
		
		if(d == 0X04)LED4ON;
		if(d == 0XF4)LED4OFF;
		
		USART_SendData(USART1,d);
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);		

		//清空标志位,告诉CPU当前数据接收完毕,可以接收新的数据
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

usart_test.c

#include "stm32f4xx.h"
#include "common.h"
#include "usart.h"
#include "led.h"

void usart_test(void)
{
    led1_init();
    led2_init();
    led3_init();
    led4_init();
    LED1OFF();
    LED2OFF();
    LED3OFF();
    LED4OFF();
    usart1_init();

    while(1);
}

main.c

#include "hwconf.h"
#include "usart_test.h"

int main(void)
{
    init_board();//初始化相关时钟
    usart_test();
}

  当需要使用printf和scanf函数时需要进行重定向,《Cortex M3与M4权威指南.pdf》章节18.1 P583有提到

    A common task for beginners is to generate a simple output message of “Hello world!” In C language, this is commonly handled
 with a “printf” statement. Under the hood, the message output can be redirected to different forms of communication interfaces.
 Typically this is known as re-targeting. For example, it is very common to retarget printf to a UART during embedded software development.
    In Keil MDK-ARM (or other ARM toolchains such as DS-5 Professional), the function that needs to be implemented to support printf is “fputc.”

总结

  1.串口通信很重要,很多对外通信都会使用串口;
  2.当串口数据量太大时,可以使用DMA中断,自动接收数据保存;
  3.空闲中断可以用于接收字符串;
  4.不同类型的串口进行通信需要使用相应的转接器。

posted @ 2021-02-22 23:50  ding-ding-light  阅读(1808)  评论(0编辑  收藏  举报