本文将会把数据手册结合三个案例讲解,需要看源码可以直接看后面

但是代码一定要结合中断、收发配置部分来理解,这两部分不建议跳过!!!

串口协议层不再介绍,需要请移步:

【串口通信详解】USART/UART、RS232、RS485标准接口与协议特点解析

目录

  1. AT32 USART 功能描述
    1.1USART 引I脚说明
    1.2 波特率配置
    1.3 帧格式和配置流程
    1.4 模式选择

    1.4.1 双线单向全双工模式
    1.4.2 硬件流控模式
    1.4.3 同步模式
    1.4.4 RS485 模式

    1.5 中断
    1.6 DMA
    1.7 发送/接收配置流程

    1.7.1 发送器

    底层原理
    代码编写

    1.7.2 接收器

  2. 案例
    2.1注意
    2.2 源码

1. AT32 USART 功能描述

在这里插入图片描述

1.1 USART 引脚说明

  1. 任何 USART 双向通信至少需要两个脚:接收数据输入(RX) 和发送数据输出(TX)。
  • RX:串行数据输入端。利用过采样技术识别数据和噪音以恢复数据。

  • TX:串行数据输出端。当发送器被禁止时,输出引脚恢复到它的 I/O 端口配置。当发送器被激活,

    并且不发送数据时,TX 引脚处于高电平。在单线和智能卡模式里,此 I/O 口被同时用于数据的发送和接收。

  1. 同步模式中需要下列引脚:
  • CK:发送器时钟输出。此引脚用于同步传输的时钟,(在 Start 位和 Stop 位上没有时钟脉冲,

软件可选地,可以在最后一个数据位送出一个时钟脉冲)。数据可以在 RX 上同步被接收。这可

以用来控制带有移位寄存器的外部设备(例如 LCD 驱动器)。时钟相位和极性都是软件可编程

的。在智能卡模式里,CK 可以为智能卡提供时钟。

  1. 硬件流控制模式中需要下列引脚:
  • CTS:发送器输入端,硬件流控制模式发送使能信号。若为低电平,表明在当前数据传输结束时可继续下一次的数据发送;若为高电平,在当前数据传输结束时阻断下一次的数据发送。

  • RTS:接收器输出端,硬件流控制模式发送请求信号。若为低电平,表明 USART 准备好接收数据。

1.2 波特率配置

​ USART 波特率发生器以 PCLK 为基准,通过使用内部计数器,波特比率寄存器(USART_BAUDR)的分频系数 DIV 即为该计数器的溢出值,该计数器计满一次代表一位数据,所以每位数据位宽为 DIV

个 PCLK 周期

​ 通过配置不同的系统时钟以及在波特比率寄存器(USART_BAUDR)中写入不同的值以产生特定的波特率,该值需要在 UEN 之前写入,且 UEN=1 时,不可更改这些位。具体的运算关系见如下公式:

在这里插入图片描述

​ 这里的𝑓𝐶𝐾是指 USART 的系统时钟(即对应的 PCLK1/PCLK2)。

​ 由于 USART 的接收器和发送器共用同一个波特率发生器,并且接收器将每位数据拆分为 16 份等长的部分以实现过采样,所以数据位宽不得小于 16 个 PCLK 周期,即 DIV 中的值必须大于 16。

注意:关闭 USART 接收器或发送器会使内部计数器复位,波特率发生器中止。

1.3 帧格式和配置流程

帧格式:

 USART 一笔数据帧由起始位,数据位,停止位依次组成,最后一位数据位可以作为校验位。在起始位期间,TX 脚处于低电平,在停止位期间处于高电平。

 USART 一笔空闲帧的长度等于当前配置下数据帧的长度,但所有位都为 1,包括停止位。

 USART 一笔间隔帧的长度等于当前配置下数据帧的长度加上停止位,停止位之前的所有位都等于 0。

配置流程:

 配置控制寄存器 1(USART_CTRL1)的 bit12 数据位个数位 DBN 置 8 位(DBN=0)或 9 位(DBN=1)数据位。

 配置控制寄存器 2(USART_CTRL2)的 bit13:12 停止位个数位 STOPBN[1:0] 置 1 位(STOPBN=00), 0.5 位(STOPBN=01), 2 位(STOPBN=10), 1.5 位(STOPBN=11)停止位。

 配置控制寄存器 1(USART_CTRL1)的 bit10 奇偶校验使能位 PEN 置 1 使能校验控制,通过配置 bit9 奇偶校验选择位 PSEL 选择奇校验(PSEL=1)或偶检验(PSEL=0), 校验控制使能后数据位的 MSB 将由奇偶校验位替代,即有效数据位减少一位。

注意:对于 AT32F435/437,配置数据位个数为 7 位时,需要置位 bit28 DBN1,请参见技术手册。出于本应用笔记的通用性考虑,后文未使用该配置。

在这里插入图片描述

1.4 模式选择

​ USART 模式选择器通过软件编程配置相应寄存器的方式,使得 USART 可以根据软件的不同配置工作在不同的工作模式下,以此能与使用不同通信协议的外设之间实现数据交换。

1.双线单向全双工模式	√
2.LIN模式
3.智能卡模式
4.红外模式
5.硬件流控模式	  √
6.静默模式
7.同步模式			√
8.RS485模式		  √

稍微介绍一下,详细见附件;

1.4.1 双线单向全双工模式

​ USART 支持 NRZ 标准格式(Mark/Space),默认选择使用双线单向全双工时, TX 管脚用于数据输出, RX 管脚用于数据输入, USART 接收器和发送器相互独立,这使得 USART 可以同时进行数据发送和数据接收,以此实现全双工通信。

​ USART 在配置控制寄存器 3(USART_CTRL3)的 bit3 单线双向半双工模式使能位 SLBEN 置 1 时选择使用单线双向半双工的方式进行数据通信,在此条件下, LINEN 位,CLKEN 位, SCMEN 位以及 IRDAEN 位需置 0。此时在 USART 内部, RX 管脚无效, TX 管脚和 SW_RX 管脚互连,对 USART 来说, TX 管脚用于数据输出, SW_RX 用于数据输入,对外设来说,数据都从 TX 管脚映射的 I/O 双向传输。

1.4.2 硬件流控模式

USART 支持 CTS/RTS(Clear To Send/Request To Send)硬件流操作。

将控制寄存器 3(USART_CTRL3)的 bit8 RTS 使能位 RTSEN 置 1,bit9 CTS 使能位 CTSEN 置 1,分别开启 RTS 和 CTS 流控制。

1.4.3 同步模式

在这里插入图片描述

1.4.4 RS485 模式

在这里插入图片描述

1.5 中断

USART 中断发生器是 USART 中断的控制中枢, USART 中断产生器会实时监测 USART 内部的中断源,并根据软件配置的相应中断源的中断使能位, 以此决定是否产生中断, 下表所示为 USART 的中断源以及相应的中断使能位,对相应的中断使能位置 1 时,即可在相应事件出现后产生中断。

在这里插入图片描述

1.6 DMA

在这里插入图片描述

1.7 发送/接收配置流程

1.7.1 发送器
底层原理

在这里插入图片描述

代码编写

在这里插入图片描述

在这里插入图片描述

1.7.2 接收器

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

案例

直接将三个案例放在一起了,注意:

  1. 轮询其实就是放到主函数里面轮询检测标志位,中断使能后就在处理函数里面进行检测标志位;
  2. 引脚互换
  3. 接收中断使能usart_transmit_receive_pin_swap

usart_transmit_receive_int(串口中断接收,手动发送字符串、数组、数字)

main.c

#include "at32a423_board.h"
#include "at32a423_clock.h"

//#include "at32a423_usart.h"
#include "my_usart.h"
#include "string.h"

uint8_t RxData;			//定义用于接收串口数据的变量

/**
  * @brief  main function.
  * @param  none
  * @retval none
  */
int main(void)
{
  system_clock_config();
	
  at32_board_init();
	
	
  usart_configuration();
	
	uint8_t text[30] = "fack! usart.\r\n";

  while(1)
  {  
	  if(at32_button_state() != RESET)					//按下则进行发送
	  {
		  delay_ms(5);
		  if(at32_button_state() != RESET)UART_Write_Blocking(text, sizeof(text));		//发送字符串
	  }
	  
	  if(Serial_GetRxFlag() == 1)						//如果接收到数据
	  {
		at32_led_toggle(LED3);
		  
		  delay_ms(500);
		 //printf("suceed!");
	  }

	  
  }
}

my_usart

#include "my_usart.h"

uint8_t Serial_RxData;		//定义串口接收的数据变量
uint8_t Serial_RxFlag;		//定义串口接收的标志位变量

uint8_t Serial_TxData;		//定义串口发送的数据变量
uint8_t Serial_TxFlag;		//定义串口发送的标志位变量

#define COUNTOF(a)                       (sizeof(a) / sizeof(*(a)))
#define USART2_TX_BUFFER_SIZE            (COUNTOF(usart2_tx_buffer) - 1)
#define USART3_TX_BUFFER_SIZE            (COUNTOF(usart3_tx_buffer) - 1)

uint8_t usart2_tx_buffer[] = "usart transfer by interrupt: usart2 -> usart3 using interrupt";
uint8_t usart3_tx_buffer[] = "usart transfer by interrupt: usart3 -> usart2 using interrupt";
uint8_t usart2_rx_buffer[USART3_TX_BUFFER_SIZE];
uint8_t usart3_rx_buffer[USART2_TX_BUFFER_SIZE];
volatile uint8_t usart2_tx_counter = 0x00;
volatile uint8_t usart3_tx_counter = 0x00;
volatile uint8_t usart2_rx_counter = 0x00;
volatile uint8_t usart3_rx_counter = 0x00;
uint8_t usart2_tx_buffer_size = USART2_TX_BUFFER_SIZE;
uint8_t usart3_tx_buffer_size = USART3_TX_BUFFER_SIZE;

/**
  * @brief  config usart
  * @param  none
  * @retval none
  */
void usart_configuration(void)
{
	
  gpio_init_type gpio_init_struct;

  /* enable the usart2 and gpio clock */
  crm_periph_clock_enable(CRM_USART2_PERIPH_CLOCK, TRUE);
  crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);

  gpio_default_para_init(&gpio_init_struct);

  /* configure the usart2 tx, rx pin */
  gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
  gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;
  gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
  gpio_init_struct.gpio_pins = GPIO_PINS_2 | GPIO_PINS_3;
  gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  gpio_init(GPIOA, &gpio_init_struct);
  gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE2, GPIO_MUX_7);	//tx
  gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE3, GPIO_MUX_7);	//rx

	/* config usart nvic interrupt */
  nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
  nvic_irq_enable(USART2_IRQn, 0, 0);

  /* configure usart2 param */
  usart_init(USART2, 115200, USART_DATA_8BITS, USART_STOP_1_BIT);	// USART2 初始化配置
	 /*使能 USART2 收发 */
  usart_transmitter_enable(USART2, TRUE);							// USART2 发送使能
  usart_receiver_enable(USART2, TRUE);								// USART2 接收使能

  /* enable usart2 and usart3 interrupt */
  usart_interrupt_enable(USART2, USART_RDBF_INT, TRUE);				//使能 USART2 的接收 缓冲区 非空中断
   usart_enable(USART2, TRUE);										//使能 USART2 模块

  //usart_interrupt_enable(USART2, USART_TDBE_INT, TRUE);			//使能 USART2 的发送数据 缓冲区 空中断

}


/**
  * 函    数:USART2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART2_IRQHandler(void)
{
    if(usart_flag_get(USART2, USART_RDBF_FLAG) != RESET) // 接收数据缓冲区满标志
    {
		//接收不定长数据meset
		//
		Serial_RxFlag = 1;										//置接收标志位变量为1
		Serial_RxData = usart_data_receive(USART2);				//读取数据寄存器,存放在接收的数据变量
		printf("Received data from USART2: %s\r\n", &Serial_RxData);	//board中已将usart1的tx重定向printf,测试使用

		//void usart_flag_clear(usart_type* usart_x, uint32_t flag);
		usart_flag_clear(USART2, USART_RDBF_FLAG);			//清除USART1的RDBF标志位
															//读取数据寄存器会自动清除此标志位
															//如果已经读取了数据寄存器,也可以不执行此代码
    }
	
	/*  学习测试打开发送中断则用,平时用下面定义的发送函数即可;注意一旦缓冲区为非零,这个中断会一直产生,发送缓冲区数据。
	软件写入的值会先存储在发送数据缓冲器(TDR)中,
	当发送移位寄存器为空时,USART 会将发送数据缓冲器中的值移入到发送移位寄存器*/
//    if (usart_flag_get(USART2, USART_TDBE_FLAG) != RESET) // 发送缓冲区空标志,缓冲区非空则之前的数据未发送完毕
//    {
//		
//		while(usart_flag_get(USART2, USART_TDBE_FLAG) == RESET)//at32_led_on(LED2);//等待发送完成,进行数据发送
//		usart_flag_clear(USART2, USART_TDBE_FLAG);
//		
//		Serial_TxFlag = 1;
//		Serial_TxData = 0x00;		//相当于数据缓冲区,为零,下面不会进行数据发送,非0则发送数据,可以自己改下试试。
//		
//		usart_data_transmit(USART2, Serial_GetTxData());		//Serial_TxData就是 Serial_GetTxData()
//		
//		usart_flag_clear(USART2,USART_TDBE_FLAG);			//清除USART1的TDBE标志位
//															//读取数据寄存器会自动清除此标志位
//															//如果已经读取了数据寄存器,也可以不执行此代码
//    }
	
}

/**
  * 函    数:获取串口接收标志位
  * 参    数:无
  * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetTxFlag(void)
{
	if (Serial_TxFlag == 1)			//如果标志位为1
	{
		Serial_TxFlag = 0;
		return 1;					//则返回1,并自动清零标志位
	}
	return 0;						//如果标志位为0,则返回0
}

/**
  * 函    数:获取串口接收标志位
  * 参    数:无
  * 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)			//如果标志位为1
	{
		Serial_RxFlag = 0;
		return 1;					//则返回1,并自动清零标志位
	}
	return 0;						//如果标志位为0,则返回0
}

/**
  * 函    数:获取串口接收的数据
  * 参    数:无
  * 返 回 值:接收的数据,范围:0~255
  */
uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;			//返回接收的数据变量
}


#define MY_USART USART2

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void UART_Write_Blocking(uint8_t* data,uint16_t size)
{
	uint16_t i=0;
	while(usart_flag_get(MY_USART, USART_TDBE_FLAG) == RESET);		//传输数据缓冲区空标志
	usart_flag_clear(MY_USART, USART_TDBE_FLAG);
	for(i=0;i<size;i++)
	{
		usart_data_transmit(MY_USART, (uint16_t)data[i]);
		while(usart_flag_get(MY_USART, USART_TDC_FLAG) == RESET);
		usart_flag_clear(MY_USART,USART_TDC_FLAG);
	}
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
	while(usart_flag_get(MY_USART, USART_TDBE_FLAG) == RESET);		//传输数据缓冲区空标志
	usart_flag_clear(MY_USART, USART_TDBE_FLAG);
	
	 usart_data_transmit(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形	
	
	while(usart_flag_get(MY_USART, USART_TDC_FLAG) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
	usart_flag_clear(MY_USART,USART_TDC_FLAG);
	
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)		//遍历数组
	{
		Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
	{
		Serial_SendByte(String[i]);		//依次调用Serial_SendByte发送每个字节数据
	}
}


/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)		//根据数字长度遍历数字的每一位
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Serial_SendByte发送每位数字
	}
}

///**
//  * 函    数:使用printf需要重定向的底层函数
//  * 参    数:保持原始格式即可,无需变动
//  * 返 回 值:保持原始格式即可,无需变动
//  */
//int fputc(int ch, FILE *f)
//{
//	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
//	return ch;
//}

///**
//  * 函    数:自己封装的prinf函数
//  * 参    数:format 格式化字符串
//  * 参    数:... 可变的参数列表
//  * 返 回 值:无
//  */
//void Serial_Printf(char *format, ...)
//{
//	char String[100];				//定义字符数组
//	va_list arg;					//定义可变参数列表数据类型的变量arg
//	va_start(arg, format);			//从format开始,接收参数列表到arg变量
//	vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中
//	va_end(arg);					//结束变量arg
//	Serial_SendString(String);		//串口发送字符数组(字符串)
//}

my_usart.h

#ifndef __MY_USART_H
#define __MY_USART_H

#include "at32a423_board.h"
#include "at32a423_usart.h"

#include "stdarg.h"
//#include "stdint.h"

void usart_configuration(void);											

uint8_t Serial_GetRxFlag(void);											
uint8_t Serial_GetRxData(void);											

void UART_Write_Blocking(uint8_t* data,uint16_t size);	

void Serial_SendByte(uint8_t Byte);	
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
//void Serial_Printf(char *format, ...);

#endif

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐