前言
(1)首先感谢 李肯前辈的活动,从而申请到了RA2L1开发板的测评。
(2)学习本文之前要具备的知识:
【致敬未来的攻城狮计划】--RA2E1 开发板测评(1)keil环境配置;
【致敬未来的攻城狮计划】--RA2E1 开发板测评(2)LED闪烁;
(3)本文需要准备的材料:TLL转USB模块;
(4)本文主要介绍RA2L1串口怎么使用,以及介绍正点原子的串口通讯协议。
(5)注意:本文用于入门学习,很多深层次的内容并不会进行讲解。如果觉得讲到太肤浅,请自行阅读野火的RA6M5串口教程部分。
(6)代码仓库: gitee仓库; GitHub仓库;
SCI与UART关系
(1)SCI和UART都是串行通信技术,但是SCI(Serial Communication Interface)是相对于并行通信的,是串行通信技术的一种总称。而UART(Universal Asynchronous Receiver & Transmitter)是串行通信的一种协议。
(2)SCI和UART都是用于串行通信的,但是SCI是一种总称,而UART是一种协议。SCI和UART的区别在于:SCI可以支持同步和异步串行通信,而UART只支持异步串行通信。
(3)所以说,UART是SCI的一种,所以说,我们接下来配置UART的时候是在SCI中配置,也不要因此有疑惑。
UART的基础知识
(1)首先我们需要直到一般情况下,串口的几个参数:
<1>端口:这个是串口工具如果识别到了TTL转USB就自动搞好了。
<2>波特率:这个是需要我们自己配置的,在开发板串口初始化的时候配置,同时PC端串口工具需要与开发板串口初始化的波特率一致。
<3>校验位:这个也需要跟开发板的一样。一般不进行设置校验位。加上校验位之后,可以有效防止因为外部干扰传输导致的数据问题。因为我们仅用于入门操作。所以不设置。
<4>数据位:在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度 常被约定为 5、6、7 或 8 位长。按照大多数习惯,我们都是选择8位,因为一个字节位8bit。
<5>停止位:这个只要开发板和PC端串口工具一致就行了,一般都是设置位1。
(2)关于数据线连接问题:开发板的TXD<----->TTL转USB的RXD,开发板的TXD<----->TTL转USB的RXD,开发板的GND<----->TTL转USB的GND。 开发板的3.3V<----->TTL转USB的VCC(如果开发板已经有供电了,这个就不需要了)
串口收发程序编写
rasc配置
(1)首先为我们在 LED闪烁那一章获得了一个文件夹了,此时打开。
(2)因为我们在已经将rasc添加进入keil中了,所以打开右上角的Tools,RA Smart Configurator。
(3) IO初始化
(4)串口初始化
注意,下图,Name与keil程序编写目前没有发现存在关系。唯一作用在于改变HAL/Common stacks中的名字。
(5)如果需要使用printf函数。
(6)关闭rasc
keil编写前期基础准备
建立usart9模块
(1)在src目录下建立文件夹usart9
(2)在usart9文件夹下建立usart9.c和usart9.h。
(3)将模块加入工程
函数介绍
(1)注意,IO配置是自动的,但是串口的开关需要我们来开启。所以我们需要使用R_SCI_UART_Open()函数打开串口。
//函数声明 fsp_err_t R_SCI_UART_Open (uart_ctrl_t * const p_api_ctrl, uart_cfg_t const * const p_cfg); /*作用:打开串口 *传入参数: *p_api_ctrl:为 &g_uartx_ctrl,其中x表示,如果是串口9,x就是9.如果是串口3,x就是3. *p_cfg:为&g_uartx_cfg,其中x表示,如果是串口9,x就是9.如果是串口3,x就是3. *返回值:返回fsp_err_t类型参数,如果成功打开串口参数为FSP_SUCCESS。 */
(2)assert()函数用于判断传入的参数是否为真,如果为真,assert函数不起作用;如果为假,打印一条出错程序,然后调用abort函数来终止程序运行。
//头文件 #include <assert.h> //函数声明 void assert(int expression);
(3)R_SCI_UART_Write()用于读取将字符串输出到串口。
//函数声明 fsp_err_t R_SCI_UART_Write (uart_ctrl_t * const p_api_ctrl, uint8_t const * const p_src, uint32_t const bytes); /*作用:将指定字符串p_src输出到串口 *参数: *p_api_ctrl:为 &g_uartx_ctrl,其中x表示,如果是串口9,x就是9.如果是串口3,x就是3. *p_src:字符串的首地址,注意,是无符号字符指针类型 *bytes:指定要写入字符的数目。如果需要传入3个字符就写3 *返回值:如果数据发送成功返回FSP_SUCCESS,否则返回FSP_ERR_UNSUPPORTED */
printf函数重定义
(1)因为我们可能需要需要写printf函数进行输出。所以需要进行下列的重映射
/* 重定向 printf 输出 */ #define g_uartx_ctrl g_uart9_ctrl //如果是串口9需要重映射,就写g_uart9_ctrl #if defined __GNUC__ && !defined __clang__ int _write(int fd, char *pBuffer, int size); //防止编译警告 int _write(int fd, char *pBuffer, int size) { (void)fd; R_SCI_UART_Write(&g_uartx_ctrl, (uint8_t *)pBuffer, (uint32_t)size); while(uart_send_complete_flag == false); uart_send_complete_flag = false; return size; } #else int fputc(int ch, FILE *f) { (void)f; R_SCI_UART_Write(&g_uartx_ctrl, (uint8_t *)&ch, 1); while(uart_send_complete_flag == false); uart_send_complete_flag = false; return ch; } #endif
正点原子串口协议介绍
(1)首先,我们需要知道一件事情,就是说,如果我们在PC端发送一个字符串"hello world"。那么单片机到底是执行了一次接收中断,还是11次(hello world一共11个字符)呢?
(2)我们可以测试一下,测试方法很简单。就是在串口中断里面,加入一个延时和一个LED电平翻转的程序。(注意:加延时的目的是为了防止LED翻转太快,以至于肉眼看不见,建议500ms)如果我们PC端发送hello world,LED翻转了11次电平。那么表示每发送个字符,就会产生一次串口中断。而如果LED只翻转了1次电平,就表示一个字符串才进入一次中断。
(3)因为我之前测试过了,之前的代码也没有找到了,所以就直接说结果:单片机每接收到一个字符,就会产生一个接收中断,所以发送hello world会产生11个接收中断程序。
(4)如果是这样的话,就存在一个问题。如果我们发送hello world,想让这个字符串前面发送我们指定的字符,比如Receive:。(PC端给单片机发送hello world,收到之后返回给PC端Receive:hello world)如果只是简单的每次接收中断前面加上一个指定字符是肯定不可以的。
(5)那么我现在就需要介绍正点原子的串口通讯协议了。说实话,其实正点原子的串口通讯协议很容易理解,主要是代码写的太精炼了,所以很多小白不能够理解。
(6)在正点原子的串口通讯协议中规定了一个16bit的数据USART_RX_STA。
<1>这个数据前14bit用于记录接收到字符个数,所以需要注意,一次接收的字符串中字符个数不能够超过16384个。
<2>我们如何知道一个字符串发送结束了呢?很简单,如果接收到了回车+换行,那么就表示字符串结束了。所以我们PC端的串口工具需要打开发送新行的按键,发送新行的意思就是在每个数据后加上回车+换行。对于bit14位而言,如果接收到了0x0D(回车),那么bit14将会被置1。而bit15则是如果bit14为1,同时此时接收到的字符为0x0A(换行),那么bit15将会被置1,表示接收完成。
(7)因为这段串口协议代码太长了,所以为了代码的简洁性,所以我将他封装成了函数。需要使用这段串口协议的人可以直接复制过来,进行些许改动即可。
注意:关于变量定义我没有写成局部变量,而是全局变量。所以直接复制会有报错,需要自行添加变量。
(8)代码刨析:
<1>因为瑞萨的中断函数里面会有一个p_args变量传入,而这个变量中存放了接收到的字符,所以我这个协议里面也需要传入p_args变量。
<2>if((USART_RX_STA&0x8000)==0),用于判断bit15是否被置为1了。如果没有被置为1,那么表示接收结束,如果字符串接收还没有结束,就进入。
<3>if(USART_RX_STA&0x4000) ,判断bit14是否被置为1(可以理解为是否接收到了回车键0x0D),如果bit14被置为1了。那么表示程序结束了。
<4>我们只需要知道一件事情,RA2E1串口接收到的数据会存入p_args->data中。如果bit14为1,但是现在接收到的数据却不是0x0a(换行键),那么就表示这次数据接收出现了异常。需要重新接收。如果最后接收到的是回车+换行,就表示字符结束了。
<5>如果上一次数据不是0x0d(回车),那么表示还需要继续接收数据。进入else语句
<6>先判断此次接收到的数据是否为0x0d,如果是的就表示字符串开始结束了。否则进入else语句,将当前输入存入数组。
<7>USART_REC_LEN为我们规定的最大接收字符,USART_REC_LEN值不能够大于16384个,具体原因看上面。而当前接收的数据不能够大于我们规定的最大接收字符数。
/*用于串口通讯协议判断 *传入参数: *p_args:这个不需要我们管,系统自动会将数据传入 *返回参数:无 */ void UART_Agreement(uart_callback_args_t * p_args) { if((USART_RX_STA&0x8000)==0) //bit15没有被置为1,接收未完成 { if(USART_RX_STA&0x4000) //bit14被置为1,表示接收到了'\n'(0x0d) { if((p_args->data)!=0x0a)USART_RX_STA=0; //如果bit14被置1了,但是bit15并不是换行操作,表示接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到'\n'(0x0d) { if((p_args->data)==0x0d)USART_RX_STA|=0x4000; //如果接收到了'\n'(0x0d),bit14被置为1 else { USART_RX_BUF[USART_RX_STA&0x3FFF]=(uint8_t)(p_args->data);//如果没有接收到结束标志,继续将数据写入USART_RX_BUF[] USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0; //如果接收到的数据大于定义最大接收字节数,表示接收数据错误,重新开始接收 } } } }
keil工程实际编写代码
hal_entry.c
void hal_entry(void) { /* TODO: add your own code here */ //串口9初始化 UART9_Init(); //开机发送 printf("CSDN:qq_63922192\n"); while(1) { led_1_flicker(); led_2_flicker(); } #if BSP_TZ_SECURE_BUILD /* Enter non-secure code */ R_BSP_NonSecureEnter(); #endif }
usart9.c
#include "usart9.h" /*作用:串口9初始化,P109--TXD,P110--RXD *传入参数:无 *返回参数:无 */ void UART9_Init(void) { fsp_err_t err = FSP_SUCCESS; err = R_SCI_UART_Open(&g_uart9_ctrl,&g_uart9_cfg); assert(err == FSP_SUCCESS); } volatile bool uart_send_complete_flag = false; // 发送完成标志 uint8_t USART_RX_BUF[USART_REC_LEN] = First_String; //接收缓冲,最大USART_REC_LEN个字节.起始字符串为First_String的宏定义 /* ============================================================================================================= USART_RX_STA ============================================================================================================= bit15 | bit14 | bit13--bit0 ============================================================================================================= 接收完成标志位(0x0A,换行键) | 接收到0x0d(回车键) | 接收到的有效数据个数 ============================================================================================================= */ uint16_t USART_RX_STA = First_String_num; //接收状态标记,根据起始字符串来初始化起始字符位置 void UART_Agreement(uart_callback_args_t * p_args); //串口通讯协议 /*用于串口9的接收和发送中断 *传入参数: *p_args:这个不需要我们管,系统自动会将数据传入 *返回参数:无 */ void UART9_callback(uart_callback_args_t * p_args) { switch (p_args->event) { //如果是串口接收中断 case UART_EVENT_RX_CHAR: { UART_Agreement(p_args);//串口通讯协议判断 if(USART_RX_STA&0x8000)//如果接收完成 { USART_RX_BUF[USART_RX_STA&0x3FFF] = 0x0d;//将字符串自动换行 USART_RX_STA++; //因为增加了0x0d,所以字符串数量+1 R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t*)USART_RX_BUF, USART_RX_STA&0x3fff);//将字符串数据输出 USART_RX_STA=First_String_num; //将USART_RX_STA初始化 } break; } //如果是串口发送中断 case UART_EVENT_TX_COMPLETE: { uart_send_complete_flag = true; break; } default: break; } } /*用于串口通讯协议判断 *传入参数: *p_args:这个不需要我们管,系统自动会将数据传入 *返回参数:无 */ void UART_Agreement(uart_callback_args_t * p_args) { if((USART_RX_STA&0x8000)==0) //bit15没有被置为1,接收未完成 { if(USART_RX_STA&0x4000) //bit14被置为1,表示接收到了'\n'(0x0d) { if((p_args->data)!=0x0a)USART_RX_STA=0; //如果bit14被置1了,但是bit15并不是换行操作,表示接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到'\n'(0x0d) { if((p_args->data)==0x0d)USART_RX_STA|=0x4000; //如果接收到了'\n'(0x0d),bit14被置为1 else { USART_RX_BUF[USART_RX_STA&0x3FFF]=(uint8_t)(p_args->data);//如果没有接收到结束标志,继续将数据写入USART_RX_BUF[] USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0; //如果接收到的数据大于定义最大接收字节数,表示接收数据错误,重新开始接收 } } } } /* 重定向 printf 输出 */ #if defined __GNUC__ && !defined __clang__ int _write(int fd, char *pBuffer, int size); //防止编译警告 int _write(int fd, char *pBuffer, int size) { (void)fd; R_SCI_UART_Write(&g_uartx_ctrl, (uint8_t *)pBuffer, (uint32_t)size); while(uart_send_complete_flag == false); uart_send_complete_flag = false; return size; } #else int fputc(int ch, FILE *f) { (void)f; R_SCI_UART_Write(&g_uartx_ctrl, (uint8_t *)&ch, 1); while(uart_send_complete_flag == false); uart_send_complete_flag = false; return ch; } #endif
usart9.h
#ifndef __usart9_H #define __usart9_H #include "hal_data.h" #include <stdio.h> /********* 参数宏定义 *********/ #define USART_REC_LEN 50 //定义最大接收字节数 50 #define First_String "Receive:" //返回给PC端的起始字符串 #define First_String_num strlen(First_String) //起始字符串长度 #define g_uartx_ctrl g_uart9_ctrl //如果是串口9需要printf重映射,就写g_uart9_ctrl /********* 函数宏定义 *********/ /********* 函数声明 *********/ void UART9_Init(void); #endif
结果
(1)将TTL转USB模块的RXD,TXD,GND,3.3V连接开发板的TXD,RXD,GND,VCC。
(2)打开PC端串口工具,端口系统自动选中,如果发现端口有多个,这个就需要自行找到那个端口才是TTL转USB模块的了。波特率115200,校验位无,数据位8,停止位1。打开串口,打开发送新行,ASCII发送。
(3)程序下载进去之后按下复位键,串口将会有CSDN:qq_63922192出现。然后输入任意字符串,会返回 Receive:字符串。同时会自动换行