USUART代码例程和库函数
- 一、USUART中重要的寄存器
- 二、USART中发送数据。
- 三、接收数据
- 四、USART发送数据示例代码(print重定向)
- 五、USART接收数据示例代码
- 六、USART常用的库函数
- usart_deinit():复位外设USART
- usart_baudrate_set():配置USART波特率
- usart_parity_config():配置USART奇偶校验
- usart_word_length_set():配置USART字长
- usart_stop_bit_set():配置USART停止位
- usart_enable():使能USART
- usart_disable():失能USART
- usart_transmit_config():USART发送配置
- usart_receive_config():USART接收配置
- usart_data_transmit():USART发送数据功能
- usart_data_receive():USART接收数据功能
- usart_flag_get():获取USART状态寄存器标志位
- usart_flag_clear():清除USART状态寄存器标志位
- usart_interrupt_enable():使能USART中断
- usart_interrupt_disable():失能USART中断
- usart_interrupt_flag_get():获取USART中断标志位状态
- usart_interrupt_flag_clear():清除USART中断标志位状态
提示:以下是本篇文章正文内容,下面案例可供参考
一、USUART中重要的寄存器
此上个寄存器的作用:
TXE表示发送缓存区【TDR】是否为空的标志。如果TDR里有暂放数据,即其没空,此时TXE=0。当把TDR里的数据COPY到移位寄存器里了且没放新数据进TDR时,TXE=1.
TC 表示从TDR里过来的数据是否全部移到外面的TX线上去了的标志。如果从TDR过来的数据全部被移送到TX线而且此时TDR里也没有新的数据,则TC=1。相反,如果刚才从TDR里过来的数据还没有全部移到外面去,或者说虽然之前TDR里的数据被移走了,但TDR里又来了新的数据,此时TC=0。平常大家把TC称之为传输结束标志,没说错,但有时可能会带来些误解,误解就出在“结束”这个字眼上。
总结:TBE,当TBE标志位置1时,可以把新数据写入到数据寄存器。
TC,当TC标志位为1时,表示一串字节发送完毕。
RBNE:当其被置1时,可以从数据寄存器中读取下一个字节。
USART_INT_RBNE:接收缓冲区不为空中断和溢出错误中断。如果开启了这个中断,每当接收到
一个字符,就会触发这个中断。在检测到 USART_INT_FLAG_RBNE 中断标志被置 1 之后,就说明当前缓冲区不为空了,有数据到来了,我们要做的操作就是把当前数据读出来然后保存
USART_INT_IDLE:空闲检测中断。如果开启了这个中断,将会在一帧数据传输完成之后触发中
断,一般用来判断一帧数据是否传输完成。
二、USART中发送数据。
发送数据的理解:数据发送时一个字节发送一次的。
注意:
a中字节移动到b中,TBE 会被置1。
在hal库中,可以看到usart_data_transmit()在低层上是给数据寄存器写入数据data。也就是说在代码中对应的发送功能,并不是指发送数据到TX引脚接收的全过程。**代码中对应的发送功能仅仅是把数据写入数据寄存器(TDR)。**剩下的过程由stem32自动完成(对应过程见图中:a到b到TX引脚)
对应在代码的表现为:
当一调用usart data transmit(),会立刻向发生数据寄存器写入一个字节。
而后的数据到移位寄存器,再到TX引脚的过程,由硬件自动完成。
由于未等待数据寄存器(即缓存寄存器)为空就直接调用usart_data_transmit(),会立即给该寄存器写了新的字节,这会导致原来的字节未移动到位移寄存器就被覆盖了。写入数据到数据寄存器(也就是usart_data_transmit())会自动清空标志位TBE,用户手册有相关介绍,这样写的时候TBE置0,等待TBE置1,然后写入下一个字节。
三、接收数据
由于数据接收时,不知道外部设备何时会来数据,这个不像发数据,发送数据完全由自己控制。
目前有二种方法,接收数据。
方法一:查询模式,查询的流程是:在主函数里面不断判断RXNT的标志位,如果置1了,就调用receivedata,读取数据寄存器。读取数据寄存器(也就是receive_data)会自动清空标志位RXNT,用户手册有相关介绍。
方法二:中断方法,其流程是:首先初始化加入开启中断的代码(开启RXNT标志位中断),当RXNT的标志位被置1,就会调用中断函数,可以自定义中断函数中写入usart_receive_data( )相关代码,来读取数据寄存器。
这就意味着,开启接收中断后,发送端来一个字节,会立刻把该字节送入到位移寄存器中,当位移寄存器中数据移动到数据寄存器中时,RXNT的标志位被置1,中断立刻开始执行,此时我们
usart_receive_data( )来获取数据寄存器的数据即可。
但是如果在中断函数内部,获取数据usart_receive_data( )后面,接一个延迟函数,而发送端来了一个新数据时,而且数据已经到了数据寄存器中,此时会再次触发一个的中断,但由于二个中断优先级一样,会先处理旧数据触发的中断,获取数据,但此时寄存器数据已经被新数据替换,所以获取到的数据是新数据,而后此中断结束,进入有新数据触发的中断,再一次获取新数据,这样就获取了二次新数据。
这里科普一下,从发送端发出数据,到进入stem32的RX端口,再到移位寄存器的过程,是stem32自发完成的。
我们需要做的的,只是根据RXNT标志位,判断数据是进入到数据寄存器,如果数据寄存器有数据,就马上读取出数据寄存器中的内容,在代码中对于操作是usart_receive_data( )。
接收一包字节的时候,可以在读取这一包字节最后一个字节时 置 一个读取结束的标志位,同时在主函数里面一直查询该标志位,当该标志为1是,就对这一包数据进行接收或者处理。而且注意在数据采集处理后,一定要对标志位清零,方便下一个数据包字节的接收,使其标志位重新起作用。
如果接受速度小于发送端发送数据的速度呢?
一包字节采用的是一个数组来存放该一包字节,然后根据自定义的标志位来时刻判断该数据是否存满一包,如果发送端只发一包数据,也不会出现数据来不及接收的现象,
但是当发送端多包字节连续发送,发送数据的速度,远大于stem32接受的速度,就会出现用于存放一包数据的数组,被下一包数据替换。
此时可以把存放数据的数组理解为数据寄存器(容器),
过程是这样的:当发送端来了数据,数据会自动进入数据寄存器,触发中断,然后自动保存在数组中,此时如果不保存接受处理该一包数据,后面如果马上来了新的数据,也会自动把原来在数组的数据替换。
就是说容器空间有限,发送来的数据会自动保存(32自动完成的)到容器中,在下一个数据来之前,需要把容器中数据保存处理,否则会被新来的数据替换掉。
四、USART发送数据示例代码(print重定向)
main文件
int main(void)
{
SystickInit();
LedDrvInit();
KeyDrvInit();
Usb2ComDrvInit();
Usb2ComTest();
while (1)
{
}
}
usb2com_drive.c文件
#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
static void Usb2ComGpioInit(void)
{
rcu_periph_clock_enable(RCU_GPIOA);
//配置TX引脚对于的管脚为复用推挽输出模式,输出模式有复用和一般模式的区别,而输出无这样的的区别。
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_10MHZ, GPIO_PIN_9);//发送引脚
//配置TX引脚对于的管脚为上拉输入或者浮空输入
//why 串口默认输出高电平,读取数据是,空闲状态和数据起始位都为低电平。
gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_10MHZ, GPIO_PIN_10);//接受引脚
}
static void Usb2ComUartInit(uint32_t baudRate)
{
/* 使能UART时钟;*/
rcu_periph_clock_enable(RCU_USART0);
/* 复位UART;*/
usart_deinit (USART0);
/* 通过USART_CTL0寄存器的WL设置字长;*/
/* 初始默认WL字长为8bit*/
usart_word_length_set(USART0, USART_WL_8BIT);
/* 通过USART_CTL0寄存器的PCEN设置校验位;*/
/* 初始默认无校验位*/
usart_parity_config(USART0, USART_PM_NONE);
/* 在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;*/
/*初始默认停止位1bit 可选0.5 1 1.5 2*/
usart_stop_bit_set(USART0, USART_STB_1BIT);
/* 在USART_BAUD寄存器中设置波特率;*/
usart_baudrate_set(USART0, baudRate);
/* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
/* 在USART_CTL0寄存器中置位UEN位,使能UART;*/
usart_enable(USART0);
}
void Usb2ComTest(void)
{
for (uint8_t i = 0; i <= 250; i++)
{
usart_data_transmit(USART0, i);//发送数据
while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
//当TBE为0,等待,当TBE为0时,表示TXR数据寄存器有数据,需要继续等待
//当TBE为1,跳出循环,表示TXR数据寄存器无数据,可以继续向位移寄存器下一个数据。
总结:RENE和TBE标志位为1是都表示已经准备好的意思,可以开始接受和发送下一个数据。TC为1时表示数据发送完毕。
}
// while (RESET == usart_flag_get(g_uartHwInfo.uartNo, USART_FLAG_TC));
// usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_DISABLE);
//TC为1时,跳出循环,表示位移寄存器数据(无数据),即发送完毕。
//TC为0时,等待,表示位移寄存器数据有数据,需要继续等待。
}
/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return
***********************************************************
*/
void Usb2ComDrvInit(void)
{
Usb2ComGpioInit();
Usb2ComUartInit(115200);
}
/**
***********************************************************
* @brief printf函数默认打印输出到显示器,如果要输出到串口,
必须重新实现fputc函数,将输出指向串口,称为重定向
* @param
* @return
***********************************************************
*/
int fputc(int ch, FILE *f)
{
usart_data_transmit(USART0, (uint8_t)ch);
while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
return ch;
}
五、USART接收数据示例代码
main文件
#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
#include "led_drv.h"
#include "key_drv.h"
#include "systick.h"
#include "usb2com_drv.h"
int main(void)
{
SystickInit();
LedDrvInit();
KeyDrvInit();
Usb2ComDrvInit();
while (1)
{
Usb2ComTask();
}
}
usb2com_drv.c
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include "gd32f30x.h"
#include "led_drv.h"
typedef struct
{
uint32_t uartNo;
rcu_periph_enum rcuUart;
rcu_periph_enum rcuGpio;
uint32_t gpio;
uint32_t txPin;
uint32_t rxPin;
uint8_t irq;
} UartHwInfo_t;
static UartHwInfo_t g_uartHwInfo = {USART0, RCU_USART0, RCU_GPIOA, GPIOA, GPIO_PIN_9, GPIO_PIN_10, USART0_IRQn};
static void Usb2ComGpioInit(void)
{
rcu_periph_clock_enable(g_uartHwInfo.rcuGpio);
gpio_init(g_uartHwInfo.gpio, GPIO_MODE_AF_PP, GPIO_OSPEED_10MHZ, g_uartHwInfo.txPin);
gpio_init(g_uartHwInfo.gpio, GPIO_MODE_IPU, GPIO_OSPEED_10MHZ, g_uartHwInfo.rxPin);
}
static void Usb2ComUartInit(uint32_t baudRate)
{
/* 使能UART时钟;*/
rcu_periph_clock_enable(g_uartHwInfo.rcuUart);
/* 复位UART;*/
usart_deinit (g_uartHwInfo.uartNo);
/* 通过USART_CTL0寄存器的WL设置字长;*/
//usart_word_length_set(g_uartHwInfo.uartNo, USART_WL_8BIT);
/* 通过USART_CTL0寄存器的PCEN设置校验位;*/
//usart_parity_config(g_uartHwInfo.uartNo, USART_PM_NONE);
/* 在USART_CTL1寄存器中写STB[1:0]位来设置停止位的长度;*/
//usart_stop_bit_set(g_uartHwInfo.uartNo, USART_STB_1BIT);
/* 在USART_BAUD寄存器中设置波特率;*/
usart_baudrate_set(g_uartHwInfo.uartNo, baudRate);
/* 在USART_CTL0寄存器中设置TEN位,使能发送功能;*/
usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE);
/* 在USART_CTL0寄存器中设置TEN位,使能接收功能;*/
usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE);
/* 使能串口接收中断;*/
usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_RBNE);
/* 使能串口中断;*/
nvic_irq_enable(g_uartHwInfo.irq, 0, 0);
/* 在USART_CTL0寄存器中置位UEN位,使能UART;*/
usart_enable(g_uartHwInfo.uartNo);
}
/**
***********************************************************
* @brief USB转串口硬件初始化
* @param
* @return
***********************************************************
*/
void Usb2ComDrvInit(void)
{
Usb2ComGpioInit();
Usb2ComUartInit(115200);
}
/**
***********************************************************************
包格式:帧头0 帧头1 数据长度 功能字 LED编号 亮/灭 异或校验数据
0x55 0xAA 0x03 0x06 0x00 0x01 0xFB
***********************************************************************
*/
#define FRAME_HEAD_0 0x55
#define FRAME_HEAD_1 0xAA
#define CTRL_DATA_LEN 3 //数据域长度
#define PACKET_DATA_LEN (CTRL_DATA_LEN + 4) //包长度
#define FUNC_DATA_IDX 3 //功能字数组下标
#define LED_CTRL_CODE 0x06 //功能字
#define MAX_BUF_SIZE 20
static uint8_t g_rcvDataBuf[MAX_BUF_SIZE];//用于存储一包数据 7个字节
static bool g_pktRcvd = false;//接受一包数据的标志位
typedef struct
{
uint8_t ledNo;
uint8_t ledState;
} LedCtrlInfo_t;
static void ProcUartData(uint8_t data)
{
//函数出作用域后不会销毁
//初始化也只有一次
static uint8_t index = 0;
//初始化也只有一次
//这个在循环里面,
//只要是正常数据,就会存入一次,同时index增加一次
g_rcvDataBuf[index++] = data;
switch (index)
{
case 1:
//判断第一个接受的数据是不是第一个针头
//是针头则会,重新开始接受
if (g_rcvDataBuf[0] != FRAME_HEAD_0)
{
index = 0;//置为初始
}
break;
//判断第一个接受的数据是不是第一个针头
case 2:
if (g_rcvDataBuf[1] != FRAME_HEAD_1)
{
index = 0;
}
break;
//PACKET_DATA_LEN为7,表示接受到7个字符
case PACKET_DATA_LEN:
g_pktRcvd = true;
//置为0,等待接受下一个数据包
index = 0;
break;
default:
break;
}
}
/**
***********************************************************
* @brief 对数据进行异或运算
* @param data, 存储数组的首地址
* @param len, 要计算的元素的个数
* @return 异或运算结果
***********************************************************
*/
static uint8_t CalXorSum(const uint8_t *data, uint32_t len)
{
uint8_t xorSum = 0;
for (uint32_t i = 0; i < len; i++)
{
xorSum ^= data[i];
}
return xorSum;
}
/**
***********************************************************
* @brief LED控制处理函数
* @param ctrlData,结构体指针,传入LED的编号和状态
* @return
***********************************************************
*/
static void CtrlLed(LedCtrlInfo_t *ctrlData)
{
ctrlData->ledState != 0 ? TurnOnLed(ctrlData->ledNo) : TurnOffLed(ctrlData->ledNo);
}
/**
***********************************************************
* @brief USB转串口任务处理函数 即数据解析
* @param
* @return
***********************************************************
*/
void Usb2ComTask(void)//while内部,会反复执行该函数。return和break类似
{
if (!g_pktRcvd)
{
return;
}
//如果为真,在传输数据之后,也需要把right_data_flag置为false。
g_pktRcvd = false;
if (CalXorSum(g_rcvDataBuf, PACKET_DATA_LEN - 1) != g_rcvDataBuf[PACKET_DATA_LEN - 1])
{
return;
}
//获取一包数据的功能数据[3]
if (g_rcvDataBuf[FUNC_DATA_IDX] == LED_CTRL_CODE)
{
//LED编号和状态二个字节传输给他
CtrlLed((LedCtrlInfo_t *)(&g_rcvDataBuf[FUNC_DATA_IDX + 1]));
}
}
/**
***********************************************************
* @brief 串口0中断服务函数
* @param
* @return
***********************************************************
*/
void USART0_IRQHandler(void)
{
//接受数据后,产生中断才进入该if语句
if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE) != RESET)
{
usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE);
//接受数据
uint8_t uData = (uint8_t)usart_data_receive(g_uartHwInfo.uartNo);
// 然后就读取数据内容了
//需要定义一个处理接收到的串口数据了
ProcUartData(uData);
}
}
/**
***********************************************************
* @brief printf函数默认打印输出到显示器,如果要输出到串口,
必须重新实现fputc函数,将输出指向串口,称为重定向
* @param
* @return
***********************************************************
*/
int fputc(int ch, FILE *f)
{
usart_data_transmit(g_uartHwInfo.uartNo, (uint8_t)ch);
while (RESET == usart_flag_get(g_uartHwInfo.uartNo, USART_FLAG_TBE));
return ch;
}
六、USART常用的库函数
注意:
开启中断是才采用usart_interrupt_flag_get()获取中断标志位
未开启中断,采用usart_flag_get()获取标志位。
未开启中断,usart_interrupt_flag_get()获取的值一直为0。
usart_deinit():复位外设USART
void usart_deinit(uint32_t usart_periph)
usart_periph:USART0~3/UART3~4
/* reset USART0 */
usart_deinit (USART0);
usart_baudrate_set():配置USART波特率
void usart_baudrate_set(uint32_t usart_periph, uint32_t baudval);
/* configure USART0 baud rate value */
usart_baudrate_set(USART0, 115200);
usart_parity_config():配置USART奇偶校验
void usart_parity_config(uint32_t usart_periph, uint32_t paritycfg);
paritycfg(配置USART奇偶校验):USART_PM_NONE(无校验)USART_PM_ODD(奇校验)USART_PM_EVEN(偶校验)
/* configure USART parity */
usart_parity_config(USART0, USART_PM_EVEN);
usart_word_length_set():配置USART字长
void usart_word_length_set(uint32_t usart_periph, uint32_t wlen);
wlen (配置USART字长):USART_WL_8BIT (8 bits ) USART_WL_9BIT(9 bits)
/* configure USART0 word length */
usart_word_length_set(USART0, USART_WL_9BIT);
usart_stop_bit_set():配置USART停止位
void usart_stop_bit_set(uint32_t usart_periph, uint32_t stblen);
/* configure USART0 stop bit length */
usart_stop_bit_set(USART0, USART_STB_1_5BIT);
usart_enable():使能USART
void usart_enable(uint32_t usart_periph);
/* enable USART0 */
usart_enable(USART0);
usart_disable():失能USART
void usart_disable(uint32_t usart_periph);
/* disable USART0 */
usart_disable(USART0);
usart_transmit_config():USART发送配置
void usart_transmit_config(uint32_t usart_periph, uint32_t txconfig);
txconfig(使能/失能USART发送器):
USART_TRANSMIT 使能USART发送 USART_TRANSMIT_DISABLE_ENABLE 失能USART发送
/* configure USART0 transmitter */
usart_transmit_config(USART0,USART_TRANSMIT_ENABLE);
usart_receive_config():USART接收配置
void usart_receive_config(uint32_t usart_periph, uint32_t rxconfig);
rxconfig使能/失能USART接收器:USART_RECEIVE_ENABLE 使能USART接收
USART_RECEIVE_DISABLE 失能USART接收
/* configure USART0 receiver */
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_data_transmit():USART发送数据功能
void usart_data_transmit(uint32_t usart_periph, uint32_t data);
/* USART0 transmit data */
usart_data_transmit(USART0, 0xAA);
usart_data_receive():USART接收数据功能
uint16_t usart_data_receive(uint32_t usart_periph);
uint16_t temp;
temp = usart_data_receive(USART0);
usart_flag_get():获取USART状态寄存器标志位
FlagStatus usart_flag_get(uint32_t usart_periph, usart_flag_enum flag);
usart_flag_enum flag:USART状态寄存器标志位 flag 表示标志为
/* get flag USART0 state */
FlagStatus status;
status = usart_flag_get(USART0,USART_FLAG_TBE);
usart_flag_clear():清除USART状态寄存器标志位
void usart_flag_clear(uint32_t usart_periph, usart_flag_enum flag);
例如:
/* clear USART0 flag */
usart_flag_clear(USART0,USART_FLAG_TC);
usart_interrupt_enable():使能USART中断
void usart_interrupt_enable(uint32_t usart_periph, usart_interrupt_enum
interrupt)
/* enable USART0TBE interrupt */
usart_interrupt_enable(USART0, USART_INT_TBE);
usart_interrupt_disable():失能USART中断
void usart_interrupt_disable(uint32_t usart_periph, usart_interrupt_enum
interrupt);
/* disable USART0TBE interrupt */
usart_interrupt_disable(USART0, USART_INT_TBE);
usart_interrupt_flag_get():获取USART中断标志位状态
FlagStatus usart_interrupt_flag_get(uint32_t usart_periph, usart_interrupt_flag_enum int_flag);
/* get the USART0 interrupt flag status */
FlagStatus status;
status = usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE);
usart_interrupt_flag_clear():清除USART中断标志位状态
void usart_interrupt_flag_clear(uint32_t usart_periph,
usart_interrupt_flag_enum int_flag);
/* clear the USART0 interrupt flag */
usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);