高云FPGA系列教程(8):ARM串口数据接收(中断和轮询方式)

2023-09-20 22:37:12

本文是高云FPGA系列教程第8篇文章

本篇文章介绍片上ARM Cortex-M3硬核处理器串口外设的使用,演示轮询方式和中断方式接收串口数据,并进行回环测试,基于TangNano 4K开发板。

参考文档:Gowin_EMPU(GW1NS-4C)软件编程 参考手册

1. GW1NSR-4C串口外设简介

GW1NSR-4C ARM部分共有2个串口外设,都挂载在APB1总线上,最高支持波特率921.6Kbit/s,无奇偶校验位,8位数据位,1位停止位,支持高速测试模式 HSTM(High Speed Test Mode),即每个时钟周期输出1位数据,可以在短时间内传输大量数据。

官方手册上没有描述发送和接收缓存FIFO的深度,所以不确定是否支持缓存。

2. FPGA配置

FPGA部分需要在云源软件中手动使能EMPU串口外设,如下图所示。


不需要其他配置,使用起来非常简单。

3. 常用函数

高云串口驱动函数常用的有以下几个:

//串口初始化,指定波特率和中断使能,高速测试模式等
ErrorStatus UART_Init(UART_TypeDef* UARTx, UART_InitTypeDef* UART_InitStruct)
//获取接收缓存区状态,当接收到数据时,返回SET
FlagStatus UART_GetRxBufferFull(UART_TypeDef* UARTx)
//获取发送缓存区状态
FlagStatus UART_GetTxBufferFull(UART_TypeDef* UARTx)
//发送一个字节
void UART_SendChar(UART_TypeDef* UARTx,char txchar)
//发送字符串
void UART_SendString(UART_TypeDef* pUARTx, char *str)
//接收一个字节,轮询或接收中断时调用,自动
char UART_ReceiveChar(UART_TypeDef* UARTx)
//获取接收中断的状态,当被触发时返回SET
ITStatus UART_GetRxIRQStatus(UART_TypeDef* UARTx)
//获取发送中断的状态
ITStatus UART_GetTxIRQStatus(UART_TypeDef* UARTx)
//清除接收中断
void UART_ClearRxIRQ(UART_TypeDef* UARTx)
//清除发送中断
void UART_ClearTxIRQ(UART_TypeDef* UARTx)

下面来介绍串口接收数据的两种方式:轮询方式和中断方式。

4. 轮询方式接收数据

初始化时不使能接收中断:

void uart0_init(uint32_t BaudRate)
{
	UART_InitTypeDef UART_InitStruct;

	UART_InitStruct.UART_Mode.UARTMode_Tx = ENABLE;
	UART_InitStruct.UART_Mode.UARTMode_Rx = ENABLE;
	UART_InitStruct.UART_Int.UARTInt_Tx = DISABLE;
	UART_InitStruct.UART_Int.UARTInt_Rx = DISABLE;
	UART_InitStruct.UART_Ovr.UARTOvr_Tx = DISABLE;
	UART_InitStruct.UART_Ovr.UARTOvr_Rx = DISABLE;
	UART_InitStruct.UART_Hstm = DISABLE;
	UART_InitStruct.UART_BaudRate = BaudRate;//Baud Rate

	UART_Init(UART0, &UART_InitStruct);
}

主循环中直接把收到的数据通过串口发送出去:

while(1)
{
    if(UART_GetRxBufferFull(UART0))
    {
        cnt_idle = 0;
        rx = UART_ReceiveChar(UART0);
        printf("rec data: %c\r\n", rx);
    }
}

这种简单粗暴的方式,会导致数据丢失,可能是串口接收部分没有FIFO导致:

我们可以采用缓冲区配合超时空闲的方式来处理,首先定义一个缓冲数组用来存储收到的数据,并通过一个计时器来判断当前是否空闲,若空闲则把数据返回:

uint8_t rx = 0;
uint8_t buf[256];
uint16_t buf_idx = 0;
uint32_t cnt_idle = 0;

//空闲超时方式接收不丢失数据
while(1)
{
    //空闲时间计数器
    if(buf_idx != 0)
    {
        cnt_idle++;
    }
    else 
    {
        cnt_idle = 0;
    }
    //数据缓存到数组中
    if(UART_GetRxBufferFull(UART0))
    {
        cnt_idle = 0;
        buf[buf_idx] = UART_ReceiveChar(UART0);
        buf_idx++;
    }
    //长时间没有接收到串口数据,把缓冲区数据返回
    if(cnt_idle > 5000)   //明显感觉=500000
    {
        UART_SendString(UART0, buf);
        cnt_idle = 0;
        buf_idx = 0;
        memset(buf, 0, sizeof(buf)/sizeof(buf[0]));
    }
}

实际测试效果很不错,数据没有任何丢失:

下面来介绍通过串口接收中断的方式来缓存数据。

5. 中断方式接收数据

初始化时使能串口接收中断,并通过NVIC开启串口中断请求。

void uart0_init(uint32_t BaudRate)
{
	UART_InitTypeDef UART_InitStruct;
	NVIC_InitTypeDef InitTypeDef_NVIC;

	UART_InitStruct.UART_Mode.UARTMode_Tx = ENABLE;
	UART_InitStruct.UART_Mode.UARTMode_Rx = ENABLE;
	UART_InitStruct.UART_Int.UARTInt_Tx = DISABLE;
	UART_InitStruct.UART_Int.UARTInt_Rx = ENABLE;   //开启接收中断
	UART_InitStruct.UART_Ovr.UARTOvr_Tx = DISABLE;
	UART_InitStruct.UART_Ovr.UARTOvr_Rx = DISABLE;
	UART_InitStruct.UART_Hstm = DISABLE;
	UART_InitStruct.UART_BaudRate = BaudRate;//Baud Rate

	UART_Init(UART0, &UART_InitStruct);

    //Enable UART0 interrupt handler
    InitTypeDef_NVIC.NVIC_IRQChannel = UART0_IRQn;
    InitTypeDef_NVIC.NVIC_IRQChannelPreemptionPriority = 1;
    InitTypeDef_NVIC.NVIC_IRQChannelSubPriority = 1;
    InitTypeDef_NVIC.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&InitTypeDef_NVIC);
}

串口中断服务函数,数据缓存到数组中,并清零空闲计数器:

void UART0_Handler(void)
{
	char rx = 0;
	
	if(UART_GetRxIRQStatus(UART0) == SET)
	{
		rx = UART_ReceiveChar(UART0);
        buf[buf_idx] = rx;
        buf_idx++;
        cnt_idle = 0;
	}
	
	UART_ClearRxIRQ(UART0);
}

需要注释掉系统默认提供的串口中断服务函数,否则编译会报错。

主循环中通过一个计数器来判断串口是否空闲,当超时没有收到新的数据时,认为串口空闲,把缓冲区的数据返回:

uint8_t rx = 0;
uint8_t buf[256];
uint16_t buf_idx = 0;
uint32_t cnt_idle = 0;

while(1)
{
    //长时间没有接收到串口数据
    if(buf_idx != 0)
        cnt_idle++;
    else 
        cnt_idle = 0;

    if(cnt_idle > 5000)   //明显感觉=500000
    {
        printf("rx: %s", buf);
        cnt_idle = 0;
        buf_idx = 0;
        memset(buf, 0, sizeof(buf)/sizeof(buf[0]));
    }
}

下载,运行,数据完整:


本文是高云FPGA系列教程第8篇文章

更多推荐

指针进阶(3)

9.模拟实现排序函数这里我们使用冒泡排序算法,模拟实现一个排序函数,可以排序任意类型的数据。这段代码可以排序整型数据,我们需要在这段代码的基础上进行改进,使得它可以排序任意类型的数据。#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>voidbubble_sort(inta

Kafka 时间轮算法

文章目录前言Java任务调度TimerDelayedWorkQueue的最小堆实现时间轮Kafka中时间轮实现前言Kafka中存在大量的延时操作。发送消息-超时+重试机制的延时。ACKS确认机制的延时。Kafka并没有使用JDK自带的Timer或者DelayQueue来实现延迟的功能,而是基于时间轮自定义了一个用于实现

Kafka核心原理

一、kafka安装步骤(1)配置profile文件vim/etc/profile//KAFKAexportKAFKA_HOME=/opt/soft/kafka212exportPATH=$KAFKA_HOME/bin:$PATHsource/etc/profile(2)创建kfkdata目录cd/opt/soft/ka

最佳实践:TiDB 业务写变慢分析处理

作者:李文杰数据架构师,TUG广州地区活动组织者在日常业务使用或运维管理TiDB的过程中,每个开发人员或数据库管理员都或多或少遇到过SQL变慢的问题。这类问题大部分情况下都具有一定的规律可循,通过经验的积累可以快速的定位和优化。但是有些情况下不一定很好排查,尤其涉及到内核调优等方向时,如果事先没有对各个组件的互访关系、

实现高效消息传递:使用RabbitMQ构建可复用的企业级消息系统

文章目录前言1.安装erlang语言2.安装rabbitMQ3.内网穿透3.1安装cpolar内网穿透(支持一键自动安装脚本)3.2创建HTTP隧道4.公网远程连接5.固定公网TCP地址5.1保留一个固定的公网TCP端口地址5.2配置固定公网TCP端口地址前言RabbitMQ是一个在AMQP(高级消息队列协议)基础上完

GODIVA论文阅读

论文链接:GODIVA:GeneratingOpen-DomaInVideosfromnAturalDescriptions文章目录摘要引言相关工作Video-to-videogenerationText-to-imagegenerationText-to-videogenerationGODIVA方法逐帧视频自动编码

【Java 基础篇】Executors工厂类详解

在多线程编程中,线程池是一项重要的工具,它可以有效地管理和控制线程的生命周期,提高程序的性能和可维护性。Java提供了java.util.concurrent包来支持线程池的创建和管理,而Executors工厂类是其中的一部分,它提供了一些方便的方法来创建不同类型的线程池。本文将详细介绍Executors工厂类的使用方

Android 使用Camera1实现相机预览、拍照、录像

1.前言本文介绍如何从零开始,在Android中实现Camera1的接入,并在文末提供Camera1Manager工具类,可以用于快速接入Camera1。AndroidCamera1API虽然已经被Google废弃,但有些场景下不得不使用。并且Camera1返回的帧数据是NV21,不像Camera2、CameraX那样

【C++】C 语言 和 C++ 语言中 const 关键字分析 ② ( const 常量分配内存时机 | const 常量在编译阶段分配内存 )

文章目录一、const常量内存分配时机二、使用如下代码验证const常量内存分配时机三、分析验证结果-const常量在编译阶段分配内存一、const常量内存分配时机在上一篇博客中,讲到了获取const常量的地址,代码如下://定义常量//该常量定义在了符号表中//符号表不在内存四区中,是另外一种机制constinta=

Pytorch 深度学习实践 day01(背景)

准备线性代数,概率论与数理统计,Python理解随机变量和分布之间的关系人类智能和人工智能人类智能分为推理和预测推理:通过外界信息的输入,来进行的推测预测:例如,看到一个真实世界的实体,把它和抽象概念联系起来人工智能(机器学习):把以前我们用来做推理或预测的大脑,变成算法在机器学习和深度学习中,常用的是监督学习,即有标

【深度学习实验】线性模型(三):使用Pytorch实现简单线性模型:搭建、构造损失函数、计算损失值

目录一、实验介绍二、实验环境1.配置虚拟环境2.库版本介绍三、实验内容0.导入库1.定义线性模型linear_model2.定义损失函数loss_function3.定义数据4.调用模型5.完整代码一、实验介绍使用Pytorch实现线性模型搭建构造损失函数计算损失值二、实验环境本系列实验使用了PyTorch深度学习框架

热文推荐