【雅特力AT32】串口 Usart 入门实战:轮询、中断、收发管脚交换(SWAP)功能
本文将会把数据手册结合三个案例(串口轮询、中断、引脚交换)讲解,需要看源码可以直接看后面。但是代码一定要结合**中断、收发配置**部分来理解,这两部分不建议跳过!!!串口协议层不再接收,需要请移步:[【串口通信详解】USART/UART、RS232、RS485标准接口与协议特点解析](https://blog.csdn.net/Thmos_vader/article/details/1411979
本文将会把数据手册结合三个案例讲解,需要看源码可以直接看后面。
但是代码一定要结合中断、收发配置部分来理解,这两部分不建议跳过!!!
串口协议层不再介绍,需要请移步:
【串口通信详解】USART/UART、RS232、RS485标准接口与协议特点解析
目录
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.1注意
2.2 源码
1. AT32 USART 功能描述
1.1 USART 引脚说明
- 任何 USART 双向通信至少需要两个脚:接收数据输入(RX) 和发送数据输出(TX)。
-
RX:串行数据输入端。利用过采样技术识别数据和噪音以恢复数据。
-
TX:串行数据输出端。当发送器被禁止时,输出引脚恢复到它的 I/O 端口配置。当发送器被激活,
并且不发送数据时,TX 引脚处于高电平。在单线和智能卡模式里,此 I/O 口被同时用于数据的发送和接收。
- 在同步模式中需要下列引脚:
- CK:发送器时钟输出。此引脚用于同步传输的时钟,(在 Start 位和 Stop 位上没有时钟脉冲,
软件可选地,可以在最后一个数据位送出一个时钟脉冲)。数据可以在 RX 上同步被接收。这可
以用来控制带有移位寄存器的外部设备(例如 LCD 驱动器)。时钟相位和极性都是软件可编程
的。在智能卡模式里,CK 可以为智能卡提供时钟。
- 在硬件流控制模式中需要下列引脚:
-
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 接收器
案例
直接将三个案例放在一起了,注意:
- 轮询其实就是放到主函数里面轮询检测标志位,中断使能后就在处理函数里面进行检测标志位;
- 引脚互换
- 接收中断使能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
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)