串口基础知识
串口介绍
串口是指外设和处理器之间通过数据信号线、地线和控制线等,按位进行传输数据的一种通讯方式。尽管传输速度比并行传输低。但串口可以在使用一根线发送数据的同时用另一根线接收数据。这种通信方式使用的数据线少,在远距离通信中可以节约通信成本。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验位,这些参数在两个通信端口之间必须一致。
串口通信参数介绍
- 波特率: 衡量通信速度的参数,它表示每秒钟传送的bit的个数。
- 数据位: 衡量通信中实际数据位的参数,表示一个信息包里包含的数据位的个数。
- 停止位: 用于表示单个信息包的最后位,典型值为1、1.5和2位。由于数据是在传输线上传输的,每个设备都有自己的时钟,很有可能在通信过程中出现不同步,停止位不仅仅表示传输的结束,还能提供校正时钟同步的机会。停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率也越慢。
- 奇偶检验位: 表示一种简单的检查错误的方式。
- 硬件数据流控:通过使用专用的控制线(通常称为RTS和CTS来协调发送方和接收方之间的数据传输过程,以防止数据丢失或溢出。
- 关于更为详细的介绍请搜索百度。
串口工作模式
串口可以工作在异步全双工 、同步半双工 和单线半双工模式下。
- 异步全双工:
- 异步:指的是通信双方不需要共享一个共同的时钟信号。数据传输的起始和结束通过特定的帧格式、起始位、停止位或者特定的同步字符来标识,允许发送和接收端各自独立工作,不必严格同步它们的时钟。这使得异步通信更加灵活,但可能引入一些开销,如额外的同步信息。
- 全双工:表示数据可以同时在两个方向上传输。也就是说,通信双方都能同时发送和接收数据,如同有两个独立的通道,互不影响,提高了通信效率,适合于需要高速、双向实时通信的场景,如以太网通信。
- 同步半双工:
- 同步:在同步通信中,发送和接收设备之间需要一个共同的时钟信号,用以同步数据的传输节奏。这通常通过一个时钟引脚或在数据流中嵌入同步位来实现。同步通信可以提供更高的数据传输速率,因为它减少了用于数据包同步的开销。
- 半双工:半双工模式允许数据在两个方向上传输,但不能同时进行。通信双方需要轮流进行发送和接收。例如,经典的RS-485总线在半双工模式下,一次只能进行发送或接收操作,需要通过控制线切换方向。这种方式适用于对实时性要求不高、双向通信但不需要同时收发的场合。
- 单线半双工:
- 单线半双工:这种模式通常特指使用一根信号线既用于发送也用于接收数据,但不能同时进行,属于半双工通信的一种特殊形式。
串口通信协议
串口在进行通信的时候会按照数据包的形式进行发送,帧格式如下图。
串口通信是一位一位地传输,每传输一个字节总是以起始位开始,以停止位结束,字符之间没有固定的时间间隔要求。每一个字符的前面都有一位起始位(低电平),后面由8位数据位组成,如果开启了校验位,则最后一位数据位是校验位,最后是停止位。停止位后面是不定长的空闲位,停止位和空闲位都规定为高电平。
SCI 简介
SCI(Serial Communications Interface),意为串行通信接口,是相对与并行通信的概念,是串行通信技术的一种总称,包括了 UART, SPI 等串行通信技术。 R7FA6E2 的 SCI 模块是一个有 2 个通道 (SCI0与SCI9)异步和同步串行接口。
其中:异步接口(UART和异步通信接口适配器(ACIA))、8位时钟同步接口、简单IIC(仅限主)、简单SPI等
注:详细信息查看数据数据手册 第764页。
SCI 结构框图
串口回环实验
UART(通用异步收发传输器)只需要两根信号线 —— 发送端(TX)和接收端(RX),就能实现设备间的双向数据传输。因为对硬件要求极低,很多功能模块都预留了 UART 接口,比如常见的 GSM 短信模块、WiFi 联网模块、蓝牙通信模块等。不过要注意哦,硬件连接时还需要一根 "共地线",让两台设备的电压参考点一致,数据传输才更稳定~
接下来,我们来编写一个程序实现开发板与电脑通信,在开发板上电时通过 UART 发送一串字符串给电脑,然后开发板进入中断接收等待状态,如果电脑有发送数据过来,开发板就会产生中断,我们在中断服务函数接收数据,并马上把数据返回发送给电脑。
开发环境搭建
首先我们需要去 FSP Configuration -> PinS -> Peripherals -> Connectivity:SCI -> SCI0 ,接下来我们需要配置PIN Group Select 为模式A only,Operation Mode ,选择Asynchronous UART 。如下图所示:
在配置界面底部点击 “Stack”,加入串口 UART 模块,如下图所示:
接下来,我们需要去配置相关属性,如下图所示:
需要在窗口中配置回调函数(Callback)名称等其他的属性按照默认的配置即可。如下图所示:
注:Channel的通道取决于CSIx中的x的参数。
这里需要配置一下串口的缓存,BSP -> 属性 -> RA Common -> Heap size(bytes),这个推荐最低是0x1000(4KB),这个根据实际情况自己填写,如下图所示:
注:这个堆大小的设置是可以被8整除的才可以。
这里我们填写 4KB ,如下图所示:
填写完毕后开始生成代码,如下图所示:
代码编写
然后再 src 下创建一个 uart 的文件夹,在里面创建 bsp_uart.c 与 bsp_uart.h 两个文件,创建一个 Applay 的文件夹,在里面创建 app.c 与 app.h 两个文件。
#include "Apply\app.h"
#include "uart\bsp_uart.h"
void Run(void)
{
UART0_Init();
printf("欢迎使用立创·地奇星RA6E2开发板\r\n");
printf("接下来开始串口循环实验,请输入回环内容:\r\n");
while(1);
}
2
3
4
5
6
7
8
9
10
11
#ifndef __APP_H
#define __APP_H
#include "hal_data.h"
#include <stdio.h>
void Run(void);
#endif
2
3
4
5
6
7
8
9
#include "bsp_uart.h"
// 发送完成标志位
volatile bool Uart0_Send_Flag = false;
// 接收完成标志位
volatile bool Uart0_Receive_Flag = false;
// 写入的字节
uint32_t bytes = 1;
//调试串口 UART0 初始化
void UART0_Init(void)
{
fsp_err_t err = R_SCI_UART_Open (&g_uart0_ctrl, &g_uart0_cfg);
if (FSP_SUCCESS != err) {
printf("串口初始化失败! \n");
return;
}
}
/* 串口中断回调 */
void uart0_callback(uart_callback_args_t *p_args)
{
switch (p_args->event)
{
case UART_EVENT_RX_CHAR: //收到数据
{
R_SCI_UART_Write(&g_uart0_ctrl, (uint8_t*) &(p_args->data), bytes);
break;
}
case UART_EVENT_RX_COMPLETE: //接收完整的事件
{
Uart0_Receive_Flag = true;
break;
}
case UART_EVENT_TX_COMPLETE://发送完整的事件
{
Uart0_Send_Flag = true;
break;
}
default:
break;
}
}
//串口重定义
// 函数前置声明(仅需声明一次)
int __io_putchar(int ch);
int _write(int fd, char *pBuffer, int size);
int _close(int fd);
int _lseek(int fd, off_t ptr, int dir);
int _read(int fd, char *pBuffer, int size);
int _fstat(int fd, struct stat *pStat);
int _isatty(int fd);
// 弱符号实现(仅需实现一次)
__attribute__((weak)) int _close(int fd) {
(void)fd; // 忽略未使用参数
return -1;
}
__attribute__((weak)) int _lseek(int fd, off_t ptr, int dir) {
(void)fd; (void)ptr; (void)dir;
return -1;
}
__attribute__((weak)) int _read(int fd, char *pBuffer, int size) {
(void)fd; (void)pBuffer; (void)size;
return 0;
}
__attribute__((weak)) int _fstat(int fd, struct stat *pStat) {
(void)fd; (void)pStat;
return -1;
}
__attribute__((weak)) int _isatty(int fd) {
(void)fd;
return 1;
}
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE {
fsp_err_t err;
err = R_SCI_UART_Write(&g_uart0_ctrl, (uint8_t *)&ch, 1);
if (FSP_SUCCESS != err) __BKPT();
while (Uart0_Send_Flag == false);
Uart0_Send_Flag = false;
return ch;
}
int _write(int fd, char *pBuffer, int size) {
(void)fd; // 忽略未使用参数
R_SCI_UART_Write (&g_uart0_ctrl, (uint8_t*) pBuffer, (uint32_t) size);
while (Uart0_Send_Flag == false);
Uart0_Send_Flag = false;
return size;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#ifndef __BSP_UART_H_
#define __BSP_UART_H_
#include "hal_data.h"
#include "stdio.h"
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h> // 添加这个头文件以获得struct stat定义
#include <stdint.h>
void UART0_Init(void);
void uart0_callback(uart_callback_args_t *p_args);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "hal_data.h"
#include "Apply\app.h"
FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER
/*******************************************************************************************************************//**
* main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
* is called by main() when no RTOS is used.
**********************************************************************************************************************/
void hal_entry(void)
{
/* TODO: add your own code here */
Run();
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
实验现象
实物连接
烧写代码之后,打开串口调试助手,输入:不靠卖板赚钱,以培养中国工程师为已任。也会输出对应的数据,打印信息如下图所示。
注:如果串口助手出现的中文乱码,建议将编码格式修改成UTF-8格式。