串口通讯
2.1 串口简介
串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。下面我们分别对串口通讯协议的物理层及协议层进行讲解。
2.2 串口核心参数
串口核心参数有波特率、起始位、数据位、停止位和校验位。
在串口异步通讯中,衡量通信性能的一个非常重要的参数就是通信速率,通常以比特率(Bitrate)来表示。比特率是每秒钟传输二进制代码的位数,单位是:位/秒(bps)。如每秒钟传送240个字符,帧格式为1位起始位+8位数据位+1位停止位,即传输一个字符需要10位数据。这时的比特率为:
10位×240个/秒 = 2400bps
串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑 0的数据位表示,而数据包的停止信号可由 0.5、1、1.5 或 2 个逻辑 1 的数据位表示,只要双方约定一致即可。
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或 8 位长。
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验 (odd)、偶校验 (even)、0 校验 (space)、1 校验 (mark) 以及无校验 (noparity)。
奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个8位长的有效数据为:01101001,此时总共有 4 个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是8位的有效数据加上1位的校验位总共 9 位。
偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。0校验是不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。
2.3 SCI简介
SCI(Serial Communications Interface),意为串行通信接口,是相对与并行通信的概念,是串行通信技术的一种总称,包括了 UART,SPI 等串行通信技术。R7FA4M2AD3CFP有 6 个通道,分别为(SCI0、SCI1、SCI3、SCI4、SCI9)。
2.4 串口全双工配置示例
1.在LED工程基础上添加串口功能。

2.打开e2 studio软件,导入工程”03USART”。



2.4.1 根据硬件接口配置串口

根据硬件原理图可知,本开发板的调试接口使用的是P110(RX)和P109(TX),根据引脚定义可以查询到该接口使用的是SCI9。本次通过对SCI9配置,即可实现串口调试功能。
1.打开配置文件

2.选择串口外设

3.选择串口引脚

3.设置串口属性:串口名字、通道号、数据位、停止位、校验位。

4.设置波特率和中断优先级

5.设置堆空间大小。

2.4.2 编写串口初始化和回调函数
1.串口初始化函数
/*
* 串口9初始化
* TX
* RX
* */
voidUSART9_Init(void)
{
fsp_err_t status;
status = R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
assert(FSP_SUCCESS==status);
}
2.串口回调函数
volatile bool usart9_sendcomplate_flag=false;
void usart9_callback(uart_callback_args_t *p_args)
{
switch(p_args->event)
{
case UART_EVENT_TX_COMPLETE:
usart9_sendcomplate_flag=true;
break;
case UART_EVENT_RX_CHAR:
R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&p_args->data, 1);
break;
case UART_EVENT_RX_COMPLETE:
break;
default:
break;
}
}
2.4.3 printf重定向实现
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
PUTCHAR_PROTOTYPE
{
fsp_err_t err;
err = R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&ch, 1);
if(FSP_SUCCESS != err) __BKPT();
while(usart9_sendcomplate_flag == false){}
usart9_sendcomplate_flag = false;
return ch;
}
int _write(int fd,char *pBuffer,int size){
(void)fd;
for(int i=0;i<size;i++)
{
__io_putchar(*pBuffer++);
}
return size;
}
#else
int fputc(int c,FILE *fp)
{
(void)fp;
R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&c, 1);
while(usart9_sendcomplate_flag==false){}
usart9_sendcomplate_flag=false;
return c;
}
#endif
在使用printf函数时,需要设置相关选项,设置如下:


2.4.4 串口功能测试
在hal_entry.c中添加初始化和测试程序。
void hal_entry(void)
{
R_IOPORT_Open(&g_ioport_ctrl,g_ioport.p_cfg);
USART9_Init();
printf("USART9 INIT OK\r\n");
uint8_t stat=0;
uint16_t time=0;
while(1)
{
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_MILLISECONDS);
time++;
if(time>=500)
{
time=0;
printf("RA4M2 Run test!\n");
}
}
#if BSP_TZ_SECURE_BUILD
R_BSP_NonSecureEnter();
#endif
}

2.5.定时器辅助串口接收
2.5.1 GPT简介
通用PWM定时器(GPT,General PWM Timer)是RA MCU的其中一种32/16位的定时器外设。 在GPT当中,可分为GPT32和GPT16,它们最主要的区别是计数器的不同。GPT32是 32 位的定时器,包含的计数器是32位的,所能计数的范围为:0 ~ 0xFFFF_FFFF;而GPT16是 16 位的定时器,包含的计数器是16位的,所能计数的范围为:0 ~ 0xFFFF。 定时器(Timer)最基本的功能就是定时,比如定时发送串口数据、定时采集 AD 数据、定时触发中断处理其它事务等等。如果把定时器与 GPIO 引脚结合起来使用的话可以实现更加丰富的功能,可以对输入信号进行计数,可以测量输入信号的脉冲宽度,可以输出单个脉冲、PWM 等波形,等等。通过定时器生成 PWM 波形信号来控制电机状态是工业控制的普遍方法,这方面知识非常值得深入了解。
GPT模块可用于计数事件、测量外部输入信号、作为通用计时器并产生周期性中断、以及输出周期性或PWM信号到GTIOC引脚。GPT也可用于输出单个脉冲,但是注意这是通过软件来实现的,GPT 硬件本身不支持输出单个脉冲(One-Shot)功能。当使用单个脉冲(One-Shot)模式时,必须要开启中断,计时器需要在脉冲周期结束后在ISR中断服务函数中被停止。
RA4M2芯片拥有共8个GPT定时器(即8个通道),其中包括4个32位GPT定时器(GPT32n (n=0~3))和4个16 位GPT定时器(GPT16m (m=4~7))。
2.5.2 GPT0计数功能配置
1.打开工程配置文件,创建定时器功能。

2.配置定时器基本功能。

2.5.3 定时器相关函数设计
1.定时器0初始化。
void Timer0_Init(void)
{
fsp_err_t status;
status = R_GPT_Open(&g_timer0_ctrl, &g_timer0_cfg);
status = R_GPT_Stop(&g_timer0_ctrl);
assert(status==FSP_SUCCESS);
}
2.定时器辅助串口接收,实现不定长数据接收。定时器回调函数实现如下:
void timer0_callback(timer_callback_args_t *p_args)
{
if(p_args->event==TIMER_EVENT_CYCLE_END)
{
R_GPT_Stop(&g_timer0_ctrl);
usart9_rx_flag=1;
}
}
3.串口9回调函数接收处理如下:
uint8_t usart9_rx_buf[USART9_RX_COUNT];
uint16_t usart9_cnt;
uint16_t usart9_rx_flag;
void usart9_callback(uart_callback_args_t *p_args)
{
switch(p_args->event)
{
case UART_EVENT_TX_COMPLETE:
usart9_sendcomplate_flag=true;
break;
case UART_EVENT_RX_CHAR:
if(usart9_rx_flag==0 && usart9_cnt<USART9_RX_COUNT)
{
R_GPT_Reset(&g_timer0_ctrl);
R_GPT_Start(&g_timer0_ctrl);
usart9_rx_buf[usart9_cnt++]=(uint8_t)p_args->data;
}
else usart9_rx_flag=1;
break;
default:
break;
}
}
2.5.4 串口不定长接收功能测试
在hal_entry.c中添加初始化和测试程序。
void hal_entry(void)
{
R_IOPORT_Open(&g_ioport_ctrl,g_ioport.p_cfg);
USART9_Init();
Timer0_Init();
printf("USART9 INIT OK\r\n");
uint16_t time=0;
uint8_t stat=0;
while(1)
{
if(usart9_rx_flag)
{
usart9_rx_buf[usart9_cnt]='\0';
printf("rx=%s\n",usart9_rx_buf);
if(strstr((char *)usart9_rx_buf,"LED1_ON"))
{
printf("LED1 ON\n");
LED1(1);
}
if(strstr((char *)usart9_rx_buf,"LED1_OFF"))
{
printf("LED1 OFF\n");
LED1(0);
}
if(strstr((char *)usart9_rx_buf,"LED2_ON"))
{
printf("LED2 ON\n");
LED2(1);
}
if(strstr((char *)usart9_rx_buf,"LED2_OFF"))
{
printf("LED2 OFF\n");
LED2(0);
}
if(strstr((char *)usart9_rx_buf,"LED3_ON"))
{
printf("LED2 ON\n");
LED3(1);
}
if(strstr((char *)usart9_rx_buf,"LED3_OFF"))
{
printf("LED2 OFF\n");
LED3(0);
}
usart9_cnt=0;
usart9_rx_flag=0;
}
time++;
R_BSP_SoftwareDelay(1,BSP_DELAY_UNITS_MILLISECONDS);
if(time>=500)
{
time=0;
stat=!stat;
LED1(stat);
}
}
#if BSP_TZ_SECURE_BUILD
R_BSP_NonSecureEnter();
#endif
}
2.6 运行效果
