《DN8P1开发指南_V1.0》第十二章 串口通信实验

第十二章 串口通信实验

本章我们将学习RA8P1的串口,教大家如何使用RA8P1的串口来发送和接收数据。本章将实现如下功能:RA8P1通过串口和上位机的对话,RA8P1在收到上位机发过来的字符串后,原原本本的返回给上位机。
本章分为如下几个小节:
12.1 串口简介
12.2 硬件设计
12.3 程序设计
12.4 下载验证

12.1串口简介
学习串口前,我们先来了解一下数据通信的一些基础概念。

12.1.1 数据通信的基础概念
在单片机的应用中,数据通信是必不可少的一部分,比如:单片机和上位机、单片机和外围器件之间,它们都有数据通信的需求。由于设备之间的电气特性、传输速率、可靠性要求各不相同,于是就有了各种通信类型、通信协议,我们最常见的有:USART、IIC、SPI、CAN、USB等。下面,我们先来学习数据通信的一些基础概念。

1. 数据通信方式
按数据通信方式分类,可分为串行通信和并行通信两种。串行和并行的对比如下图所示:

image001

图12.1.1.1 数据传输方式

串行通信的基本特征是数据逐位顺序依次传输,优点是传输线少、布线成本低、灵活度高等优点,一般用于近距离人机交互,特殊处理后也可以用于远距离,缺点就是传输速率低。
而并行通信是数据各位可以通过多条线同时传输,优点是传输速率高,缺点就是布线成本高,抗干扰能力差,适用于短距离、高速率的通信。

2. 数据传输方向
根据数据传输方向,通信又可分为全双工、半双工和单工通信。全双工、半双工和单工通信的比较如下图所示:

image003

图12.1.1.2 数据传输方式

单工是指数据传输仅能沿一个方向,不能实现反方向传输,如校园广播。
半双工是指数据传输可以沿着两个方向,但是需要分时进行,如对讲机。
全双工是指数据可以同时进行双向传输,日常的打电话属于这种情形。
这里注意全双工和半双工通信的区别:半双工通信是共用一条线路实现双向通信,而全双工是利用两条线路,一条用于发送数据,另一条用于接收数据。

3. 数据同步方式
根据数据同步方式,通信又可分为同步通信和异步通信。同步通信和异步通信比较如下图所示:

image005

图12.1.1.3 数据同步方式

同步通信要求通信双方共用同一时钟信号,在总线上保持统一的时序和周期完成信息传输。优点:可以实现高速率、大容量的数据传输,以及点对多点传输。缺点:要求发送时钟和接收时钟保持严格同步,收发双方时钟允许的误差较小,同时硬件复杂。
异步通信不需要时钟信号,而是在数据信号中加入开始位和停止位等一些同步信号,以便使接收端能够正确地将每一个字符接收下来,某些通信中还需要双方约定传输速率。优点:没有时钟信号硬件简单,双方时钟可允许一定误差。缺点:通信速率较低,只适用点对点传输。

4. 通信速率
在数字通信系统中,通信速率(传输速率)指数据在信道中传输的速度,它分为两种:传信率和传码率。
传信率:每秒钟传输的信息量,即每秒钟传输的二进制位数,单位为bit/s(即比特每秒),因而又称为比特率。
传码率:每秒钟传输的码元个数,单位为Baud(即波特每秒),因而又称为波特率。
比特率和波特率这两个概念又常常被人们混淆。比特率很好理解,我们来看看波特率,波特率被传输的是码元,码元是信号被调制后的概念,每个码元都可以表示一定bit的数据信息量。举个例子,在TTL电平标准的通信中,用0V表示逻辑0,5V表示逻辑1,这时候这个码元就可以表示两种状态。如果电平信号0V、2V、4V和6V分别表示二进制数00、01、10、11,这时候每一个码元就可以表示四种状态。
由上述可以看出,码元携带一定的比特信息,所以比特率和波特率也是有一定的关系的。
比特率和波特率的关系可以用以下式子表示:
比特率 = 波特率 * log2M
其中M表示码元承载的信息量。我们也可以理解M为码元的进制数。
举个例子:波特率为100 Baud,即每秒传输100个码元,如果码元采用十六进制编码(即M=16,代入上述式子),那么这时候的比特率就是400 bit/s。如果码元采用二进制编码(即M=2,代入上述式子),那么这时候的比特率就是100 bit/s。
可以看出采用二进制的时候,波特率和比特率数值上相等。但是这里要注意,它们的相等只是数值相等,其意义上不同,看波特率和波特率单位就知道。由于我们的所用的数字系统都是二进制的,所以有部分人久而久之就直接把波特率和比特率混淆了。

12.1.2 串口通信协议简介
串口通信是一种设备间常用的串行通信方式,串口按位(bit)发送和接收字节。尽管比特字节(byte)的串行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。串口通信协议是指规定了数据包的内容,内容包含了起始位、主体数据、校验位及停止位,双方需要约定一致的数据包格式才能正常收发数据的有关规范。在串口通信中,常用的协议包括RS-232、RS-422和RS-485等。
随着科技的发展,RS-232在工业上还有广泛的使用,但是在商业技术上,已经慢慢的使用USB转串口取代了RS-232串口。我们只需要在电路中添加一个USB转串口芯片,就可以实现USB通信协议和标准UART串行通信协议的转换,而我们开发板上的USB转串口芯片是CH340C这个芯片。关于USB转串口芯片的原理图请看12.2小节。
下面我们来学习串口通信协议,这里主要学习串口通信的协议层。
串口通信的数据包由发送设备的TXD接口传输到接收设备的RXD接口。在串口通信的协议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据,其组成如图12.1.2.1所示。

image008

图12.1.2.1 串口通信协议数据帧格式

串口通信协议数据包组成可以分为波特率和数据帧格式两部分。
1. 波特率
本章主要讲解的是串口异步通信,异步通信是不需要时钟信号的,但是这里需要我们约定好两个设备的波特率。波特率表示每秒钟传送的码元符号的个数,所以它决定了数据帧里面每一个位的时间长度。两个要通信的设备的波特率一定要设置相同,我们常见的波特率是4800、9600、115200等。

2. 数据帧格式
数据帧格式需要我们提前约定好,串口通信的数据帧包括起始位、停止位、有效数据位以及校验位。
起始位和停止位
串口通信的一个数据帧是从起始位开始,直到停止位。数据帧中的起始位是由一个逻辑0的数据位表示,而数据帧的停止位可以是0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。
有效数据位
数据帧的起始位之后,就接着是数据位,也称有效数据位,这就是我们真正需要的数据,有效数据位通常会被约定为5、6、7或者8个位长。有效数据位是低位(LSB)在前,高位(MSB)在后。
校验位
校验位可以认为是一个特殊的数据位。校验位一般用来判断接收的数据位有无错误,检验方法有:奇检验、偶检验、0检验、1检验以及无检验。下面分别介绍一下:
奇校验是指有效数据为和校验位中“1”的个数为奇数,比如一个8位长的有效数据为:10101001,总共有4个“1”,为达到奇校验效果,校验位设置为“1”,最后传输的数据是8位的有效数据加上1位的校验位总共9位。
偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为4个,所以偶校验位为“0”。
0校验是指不管有效数据中的内容是什么,校验位总为“0”,1校验是校验位总为“1”。
无校验是指数据帧中不包含校验位。
我们一般是使用无校验的情况。

12.1.3 RA8P1的 SCI 简介
RA8P1 片上集成了 SCI(Serial Communications Interface,串行通信接口)模块。根据 RA8P1 用户手册,RA8P1 提供 10 路 SCI 通道,通道号为 SCIn(n = 0 到 9)。SCI 支持异步模式、时钟同步模式、简易 SPI、Manchester、Smart Card 和 Simple LIN 等多种通信功能。本章只介绍最常用的异步 UART 通信。
RA8P1 的 SCI 在异步模式下具有如下特点:
1.支持全双工串行收发,使用 TXDn 和 RXDn 两根主要信号线。
2.支持 7 位、8 位或 9 位数据长度,可配置校验位和停止位。
3.支持内部波特率发生器,也可使用外部时钟输入。
4.支持发送中断、接收中断、发送完成中断和错误中断。
5.支持 FIFO 和非 FIFO 工作方式。
6.支持 CTS/RTS 硬件流控相关信号。
下面先通过 SCI 框图建立对 RA8P1 串口模块的整体认识。SCI 框图可以分成信号引脚、收发数据寄存器、控制寄存器、状态/中断控制、波特率发生器和 FIFO 等几个部分。

image009

图12.1.3.1 SCI 框图

1. SCI 信号引脚
SCI 与外部连接的主要引脚如下:

image011

图12.1.3.2 SCI的I/O引脚

本实验使用异步 UART 通信,只需要使用 TXDn 和 RXDn。TXDn 连接到电脑串口接收端,RXDn 连接到电脑串口发送端。若使用开发板板载 J-Link OB 虚拟串口,则开发板上已经完成 USB 转串口连接,用户只需要连接调试 USB 线并打开对应 COM 口即可。

2. 收发数据
SCI 的发送和接收由不同的数据寄存器完成:

QQ截图20261109093559

表12.1.3.1 SCI寄存器概要

当 SCI 接收到一帧有效数据后,硬件会把数据保存到 RDR,并置位接收相关状态标志,同时可以产生接收中断。程序读取 RDR 后即可取得接收到的数据。发送时,程序把待发送数据写入 TDR,SCI 会自动把数据从移位寄存器按串口帧格式发送到 TXDn 引脚。

3. 控制器
SCI 的发送、接收、通信格式、中断和错误处理由多个控制寄存器共同完成。与本实验关系最密切的是 CCR0、CCR1、CCR2 和 CCR3 等公共控制寄存器。
在异步模式下,常用配置包括:
选择异步通信模式。
设置数据长度、校验位和停止位。
设置波特率相关参数。
使能发送功能 TE 和接收功能 RE。
按需要使能发送中断、接收中断和错误中断。
使用 FSP 开发时,用户通常不直接手动写这些寄存器,而是在 FSP Configuration 的 UART 模块中设置通道号、波特率、数据位、校验位、停止位、回调函数和中断优先级,FSP 生成代码会完成底层寄存器配置。

4.时钟与波特率
波特率表示每秒传输的码元数量。对于常见二进制 UART 通信,可以近似理解为每秒传输的二进制位数。例如 115200 bit/s 表示串口每秒传输 115200 位。
RA8P1 SCI 可以使用片上波特率发生器产生串口通信时钟。异步接收时,SCI 会根据设定的基准时钟对 RXDn 输入进行采样,检测到起始位后,在每个数据位的中间位置采样,从而恢复出数据。通信双方的波特率误差不能过大,否则接收采样点会偏离有效数据区域,导致帧错误或数据错误。
在本实验中,FSP 配置串口参数为:

QQ截图20261109093613

表12.1.3.2 FSP 配置串口参数

12.2 硬件设计
1. 例程功能
本实验通过 UART9 实现开发板与上位机的串口通信。开发板周期性向上位机发送实验提示信息,同时接收上位机发送的数据(以\r\n为结束标志),并将接收到的内容回显到串口助手上,验证串口收发功能。

2. 硬件资源
1)LED 灯
LED0 – P108
2)串口(请使用跳线帽接上)
RXD9 – P208
TXD9 – P209

3. 原理图
串口硬件部分的原理图,如图 12.2.1 所示:

image013

图12.2.1串口接口原理图

UART9 的 TXD、RXD 引脚通过开发板左下角 J3 排针连接到板载 CH343 芯片,实现 USB 转串口功能。使用时需要在 J3 排针上插上两个跳线帽,将 MCU 的串口引脚与 CH343 连通,即可通过 USB 线连接电脑与开发板进行串口通信。
注意:UART 外设工作依赖 SCICLK 时钟,需在时钟配置中确保该时钟正常使能,否则串口无法正常工作。

**12.3 程序设计 **
12.3.1 FSP配置流程
注意:LED的FSP配置流程,我们已经在第八章节讲解过,这里我们不再赘述。
在FSP配置Pins Connectivity:SCI路径下配置开启SCI,如下图所示。

image015

2.Stacks 标签页中,点击"New Stack",添加SCI串口实例,如下图所示。

image017

选中Stacks 标签页中新创建的SCI实例,点击"属性",修改相关参数配置。

image019

4.最后还需选中创建的SCI实例,点击Add DTC Driver for Transmission,点击New。

image021

注意:俩个都需要进行添加DTC Driver for Transmission。

12.3.2 程序解析
1. UART驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。UART 驱动源码包括两个文件:uart.c 和 uart.h。
下面我们先解析uart.h程序中的宏定义、全局变量声明与函数声明。

#define USART_REC_LEN       200                     	/* 定义最大接收字节数 200 */
#define RXBUFFERSIZE        1                        	/* 缓存大小 */
/* 接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 */
extern uint8_t  g_usart_rx_buf[USART_REC_LEN];  
/* 接收状态标记 */
extern uint16_t g_usart_rx_sta;                   

fsp_err_t uart_init(void);                      	/* UART初始化 */
fsp_err_t uart_deinit(void);                     	/* UART反初始化 */
int uart_printf(const char *format, ...);       	/* printf重定向 */

/* 重定义printf使用自定义函数 */
#define printf uart_printf

/* 调试接口 */
#define log_d(fmt, ...)    printf("[D] " fmt, ##__VA_ARGS__)    /* 调试日志 */
#define log_i(fmt, ...)    printf("[I] " fmt, ##__VA_ARGS__)    /* 信息日志 */
#define log_w(fmt, ...)    printf( "[W] " fmt, ##__VA_ARGS__)   /* 警告日志 */
#define log_e(fmt, ...)    printf("[E] " fmt, ##__VA_ARGS__)    /* 错误日志 */

USART_REC_LEN 定义了串口接收缓冲区的最大长度,本实验设置为 200 字节。g_usart_rx_buf 是串口接收数据缓冲区,g_usart_rx_sta 是接收状态寄存器,采用位域方式管理:bit15 为接收完成标志,bit14 标记是否接收到 0x0d(回车符),低 14 位记录已接收的有效字节数。
uart_init 和 uart_deinit 分别是串口的初始化与反初始化函数,uart_printf 是自定义格式化输出函数,通过宏定义将标准printf重定向到串口输出,方便调试打印。同时封装了log_d、log_i等不同等级的调试日志宏,带统一前缀便于查看。
这里需要注意的是:UART9 的引脚复用、波特率、数据格式等参数均已通过 FSP 配置工具完成设置,系统启动时完成端口基础时钟配置,驱动中仅需调用 Open 函数完成外设使能。
下面我们再解析 uart.c 的程序,这里包含初始化函数、发送封装函数、printf 重定向函数和中断回调函数。
首先是串口初始化与反初始化函数,其定义如下:

fsp_err_t uart_init(void)
{
    fsp_err_t err = FSP_SUCCESS;
    /* 打开UART9通道,加载FSP中配置的波特率、引脚和中断参数 */
    err = R_SCI_B_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
    if (err != FSP_SUCCESS) {
        log_d("atk module uart init failed!\r\n");
    }
    return err;
}

fsp_err_t uart_deinit(void)
{
    fsp_err_t err = FSP_SUCCESS;
    /* 关闭UART9通道,释放串口驱动资源 */
    err = R_SCI_B_UART_Close(&g_uart9_ctrl);
    if (err != FSP_SUCCESS) {
        log_d("atk module uart deinit failed!\r\n");
    }
    return err;
}

uart_init 函数调用 FSP 提供的 R_SCI_B_UART_Open 接口,打开 UART9 通道,加载图形化配置的波特率、数据位、停止位等参数,使能串口外设与对应中断。初始化失败时会打印错误提示。uart_deinit 则执行相反操作,关闭串口通道,释放硬件资源。
接下来是阻塞发送封装函数:

static int uart_write_buffer(uint8_t *p_data, uint32_t length)
{
    if (length == 0U)
    {
        return 0;
    }
    /* 发送前清除完成标志,等待TX完成中断重新置位 */
    uart9_tx_complete_flag = false;
    /* 启动UART异步发送,发送完成由回调函数置位标志 */
    if (FSP_SUCCESS != R_SCI_B_UART_Write(&g_uart9_ctrl, p_data, length))
    {
        return -1;
    }
    /* 阻塞等待发送完成,保证printf数据完整输出 */
    while (uart9_tx_complete_flag == false)
    {
    }
    return 0;
}

该函数为内部静态函数,实现串口阻塞发送。先清除发送完成标志,调用 R_SCI_B_UART_Write 启动异步发送,然后循环等待发送完成标志置位,确保数据全部发送完成后再返回,保证 printf 输出的完整性。
然后是 printf 重定向函数:

int uart_printf(const char *format, ...)
{
    char tx_buf[256];
    va_list args;
    int len;
    uint32_t tx_len;
    va_start(args, format);
    /* 先把格式化字符串写入本地发送缓冲区 */
    len = vsnprintf(tx_buf, sizeof(tx_buf), format, args);
    va_end(args);
    if (len <= 0)
    {
        return len;
    }
    /* 限制实际发送长度,防止格式化结果超过缓冲区 */
    tx_len = (len < (int) sizeof(tx_buf)) ? (uint32_t) len : ((uint32_t) sizeof(tx_buf) - 1U);
    if (0 != uart_write_buffer((uint8_t *) tx_buf, tx_len))
    {
        return -1;
    }
    return len;
}

该函数实现标准格式化输出到串口的重定向。使用可变参数列表接收格式化字符串,通过 vsnprintf 将格式化后的内容写入本地缓冲区,再调用 uart_write_buffer 将数据通过串口发出。兼容不同编译器的标准库,避免直接重定向 stdout 带来的兼容性问题。
最后是串口中断回调函数:

void r_sci_uart9_callback(uart_callback_args_t *p_args)
{
    uint8_t Res;
    /* 接收到单字节数据事件,按\r\n协议更新接收状态 */
    if (UART_EVENT_RX_CHAR == p_args->event)
    {
        /* 取出本次中断收到的1字节数据 */
        Res = (uint8_t ) p_args->data;
        /* bit15未置位表示当前帧仍在接收中 */
        if ((g_usart_rx_sta & 0x8000) == 0)         	/* 接收未完成 */
        {
            if (g_usart_rx_sta & 0x4000)             	/* 接收到了0X0D */
            {
                if (Res != 0x0a)
                {
                    g_usart_rx_sta = 0;           		/* 接收错误,重新开始 */
                }
                else
                {
                    g_usart_rx_sta |= 0x8000;        	/* 接收完成了 */
                }
            }
            else                                    	/* 还没收到0X0D */
            {
                if (Res == 0x0d)
                {
                    /* 收到回车符后置位bit14,等待后续换行符 */
                    g_usart_rx_sta |= 0x4000;
                }
                else
                {
                    /* 将普通数据存入接收缓冲区,低14位作为下标 */
                    g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = Res;
                    g_usart_rx_sta++;
                    if (g_usart_rx_sta > (USART_REC_LEN - 1))
                    {
                        g_usart_rx_sta = 0;       	/* 接收数据错误,重新开始接收 */
                    }
                }
            }
        }
    }
    else if (UART_EVENT_TX_COMPLETE == p_args->event)
    {
        /* TX完成事件置位发送完成标志,释放阻塞等待 */
        uart9_tx_complete_flag = true;
    }
}

该函数是 UART9 的统一中断回调函数,处理接收和发送两类事件:
1.接收事件(UART_EVENT_RX_CHAR):每收到 1 字节数据触发一次,按照\r\n协议解析帧:先接收普通数据存入缓冲区,遇到 0x0d(回车)标记待完成,后续收到 0x0a(换行)则置位接收完成标志;若帧格式错误或数据溢出则重置接收状态,重新开始接收。
2.发送完成事件(UART_EVENT_TX_COMPLETE):一帧数据发送完成后触发,置位发送完成标志,解除发送函数的阻塞等待。

2. hal_entry.c 代码
在 hal_entry.c 里面编写如下代码:

void hal_entry(void)
{
    uint16_t times = 0;
    delay_init(1000000);    /* 初始化systick */
    led_init();             /* 初始化led */
    uart_init();            /* 初始化uart */
    while (1)
    {
        if (g_usart_rx_sta & 0x8000)            /* 接收到了数据? */
        {
            log_d("您发送的消息为:%s\r\n",g_usart_rx_buf);
            g_usart_rx_sta = 0;
        }
        else
        {
            times++;
            if (times % 5000 == 0)
            {
                log_d("\r\n正点原子 ATK-DN8P1开发板 串口实验\r\n");
                log_d("正点原子@ALIENTEK\r\n\r\n\r\n");
            }
            if (times % 200 == 0) log_d("请输入数据,以回车键结束\r\n");
        }
        /* 主循环短延时,控制程序运行节奏 */
        delay_ms(10);
    }
}

hal_entry 是 FSP 工程的系统启动入口点。程序首先完成系统级初始化:初始化延时函数、LED 外设和 UART 串口外设。
接下来在无限循环中,先判断接收状态寄存器的完成标志:如果接收到完整的一帧数据,则将接收到的内容通过串口回显打印,然后清空接收状态,准备接收下一帧。
如果没有收到数据,则通过times计数器周期性打印实验提示信息和输入提示,验证串口发送功能正常。主循环每次延时 10ms,控制程序运行节奏。

12.4 下载验证
在下载好程序后,打开串口助手,设置波特率为 115200,8 位数据位,无校验,1 位停止位,编码格式选择 UTF-8,连接开发板对应的串口号,可以观察到如图12.4.1所示现象。

image023

图12.4.1 串口助手显示信息

1.串口助手周期性收到开发板发送的实验标题和输入提示信息;
2.在串口助手中输入任意内容,勾选 “发送新行” 后点击发送,开发板会将收到的内容原封不动回显到串口助手上。
注意事项:
务必将开发板左下角 J3 排针的两个跳线帽插上,确保串口引脚与 CH343 连通;
串口助手编码格式需设置为 UTF-8,否则中文会出现乱码;
发送数据时需要以回车换行(\r\n)结尾,否则不会触发接收完成判定。
至此,本章的学习就结束了。本章学习了 RA8P1 的 UART 串口外设使用方法,掌握了 SCI UART 的 FSP 配置、串口收发驱动编写、printf 重定向以及中断回调的使用方式。串口是嵌入式开发中最常用的调试与通信接口,大家可以在此基础上扩展更多串口通信协议与应用

posted @ 2026-11-11 09:39  正点原子  阅读(7)  评论(0)    收藏  举报