【瑞萨RA_FSP】UART 编程实战

news/2025/1/10 23:58:09/

文章目录

  • 一、UART收发回显
  • 二、UART指令控制RGB灯
  • 三、基于环形队列的UART收发回显


一、UART收发回显

UART只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留UART接口来实现与其他模块或者控制器进行数据传输, 比如GSM模块,WIFI模块、蓝牙模块等等。在硬件设计时,注意还需要一根“共地线”。

我们经常使用UART来实现控制器与电脑之间的数据传输。这使得我们调试程序非常方便,比如我们可以把一些变量的值、 函数的返回值、寄存器标志位等等通过UART发送到串口调试助手,这样我们可以非常清楚程序的运行状态。

不仅仅可以将数据发送到串口调试助手,还可以在串口调试助手发送数据给控制器,控制器程序根据接收到的数据进行下一步工作。

首先,编写一个程序实现开发板与电脑通信,在开发板上电时通过UART发送一串字符串给电脑,然后开发板进入中断接收等待状态, 如果电脑有发送数据过来,开发板就会产生中断,在中断服务函数接收数据,并马上把数据返回发送给电脑。

1. 硬件设计
为利用 UART 实现开发板与电脑通信,需要用到一个USB转串口(UART)的芯片:CH340G。 CH340G 是一个USB总线的转接芯片,实现USB转UART、USB转lrDA红外或者USB转打印机接口,我们使用其USB转UART功能。 具体电路设计见下图

在下面的三块开发板的电路图中,CH340G的TXD引脚与MCU芯片 UART 的RXD引脚连接, CH340G的RXD引脚与MCU芯片 UART 的TXD引脚连接。CH340G芯片集成在开发板上,其地线(GND)已与控制器的GND连通。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

P511:SCL4 RXD
P512:SCL4 TXD

2. 软件设计

① FSP配置

在 FSP 配置界面里面点开 “Pins”-> “Peripherals”-> “Connectivity:SCI”-> “SCI4” 来配置SCI模块, 配置为 “Asynchronous UART” 模式,并选择开发板所使用的串口引脚,如下图。

在这里插入图片描述
在配置界面底部点击 “Stack”,如下图步骤加入串口UART:
在这里插入图片描述
如下图点击刚刚加入的窗口,在左下角的“属性”窗口中配置 名字(name)、通道(Channel)、回调函数(Callback)名字即可, 引脚(Pins)、波特率(Baud Rate)等其他的属性按照默认的配置即可。
在这里插入图片描述
在这里插入图片描述

使用 printf 函数时,需要使用到堆,默认情况下堆的大小为0,因此我们需要修改堆的大小。 可以在 FSP 配置界面中的“BSP”属性栏的“RA Common”中通过修改“Heap size”来设置堆区大小。 这里需要设置为 8 的整数倍,对于RA6M5推荐至少为4K(0x1000),如下图。

在这里插入图片描述
最后点右上角的 “Generate Project Content” 按钮,让软件自动生成配置代码。

② 串口初始化函数
FSP 配置并生成代码之后,首先需要使用 R_SCI_UART_Open 函数打开 SCI4 UART 模块, 我们把这层调用封装为一个 Debug_UART4_Init 函数,如下所示。

/* 调试串口 UART4 初始化 */
void Debug_UART4_Init(void)
{fsp_err_t err = FSP_SUCCESS;err = R_SCI_UART_Open (&g_uart4_ctrl, &g_uart4_cfg);assert(FSP_SUCCESS == err);
}

③ R_SCI_UART_Write函数
串口初始化完成之后,可以直接使用 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 指向要写入的字符串首地址

  • 参数 bytes 为传入的要写入的字符的数目

在使用 R_SCI_UART_Write 函数需要注意的一些事项:
若使用了 R_SCI_UART_Write() 来发送数据, 在数据发送完成之后会导致 uart_send_complete_flag 这个标志位被置位, 因此程序在调用 R_SCI_UART_Write 函数之后需要等待 uart_send_complete_flag 标志位被置位, 然后将该标志位清零。否则当连续调用 R_SCI_UART_Write 函数时可能导致发送数据丢失。 建议使用后文所述的 printf 函数将数据发送到串口。

④ 串口中断回调函数
在前面的 FSP 配置步骤的时候,设置了串口中断回调函数的名字为: debug_uart4_callback。 设置这么一个函数的原因是:每当串口发送或者接收完成一个字符时,都会默认触发串口的中断, 而在串口中断中会调用函数 debug_uart4_callback,在函数里我们需要根据不同的中断情况进行相应的处理。

因此,也需要同时在代码里面定义并实现这么函数 debug_uart4_callback。 把这个函数放到文件“bsp_debug_uart.c”中,该函数代码如下所示。

其中,需要定义一个额外的标志变量 uart_send_complete_flag 来表示串口发送数据已完成。 变量 uart_send_complete_flag 必须加上volatile,否则可能被编译器优化。

/* 发送完成标志 */
volatile bool uart_send_complete_flag = false;/* 串口中断回调 */
void debug_uart4_callback (uart_callback_args_t * p_args)
{switch (p_args->event){case UART_EVENT_RX_CHAR:{/* 把串口接收到的数据发送回去 */R_SCI_UART_Write(&g_uart4_ctrl, (uint8_t *)&(p_args->data), 1);break;}case UART_EVENT_TX_COMPLETE:{uart_send_complete_flag = true;break;}default:break;}
}

⑤ 重定向printf输出到串口
虽然可以直接使用 R_SCI_UART_Write 函数来将字符串输出到串口, 但是这个函数在很多情况下没有 printf 函数那样方便。所以需要添加一段代码来将 printf 输出重定向到串口(UART4)。

将以下的代码添加到源文件“bsp_debug_uart.c”里面。 由于不同C库的 printf 函数的底层实现不同,这里使用条件编译选择我们需要重写的函数。

/* 重定向 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_uart4_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_uart4_ctrl, (uint8_t *)&ch, 1);while(uart_send_complete_flag == false);uart_send_complete_flag = false;return ch;
}
#endif

⑥ hal_entry入口函数
C语言程序的入口函数 main 函数调用了 hal_entry 函数。 在 hal_entry 函数里面编写应用代码。

void hal_entry(void)
{/* TODO: add your own code here */LED_Init();         // LED 初始化Debug_UART4_Init(); // SCI4 UART 调试串口初始化printf("这是一个串口收发回显例程\r\n");printf("打开串口助手发送数据,接收窗口会回显所发送的数据\r\n");while(1){LED1_ON;R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);LED1_OFF;R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);}#if BSP_TZ_SECURE_BUILD/* Enter non-secure code */R_BSP_NonSecureEnter();
#endif
}

首先调用 LED_Init 函数初始化板子上的 LED 灯,然后调用 Debug_UART4_Init 函数初始化 SCI4 UART 作为调试串口来使用。 之后就可以使用 printf 函数了,调用 printf 输出提示信息到串口。 接着在 while 循环里是一段让 LED1 每隔一秒钟闪烁的程序。

二、UART指令控制RGB灯

1. 串口中断回调函数
需要在串口中断回调函数,也就是 debug_uart4_callback 函数里判断接收到的字符, 并根据所接收到的不同字符做出不同的操作。 修改 debug_uart4_callback 函数的代码,如下所示。

/* 串口中断回调 */
void debug_uart4_callback (uart_callback_args_t * p_args)
{switch (p_args->event){case UART_EVENT_RX_CHAR:{/* 根据字符指令控制RGB彩灯颜色 */switch (p_args->data){case '1':LED1_ON;break;case '2':LED2_ON;break;case '3':LED3_ON;break;case '4':LED1_OFF;break;case '5':LED2_OFF;break;case '6':LED3_OFF;break;case '7':LED1_ON; LED2_ON; LED3_ON;break;case '8':LED1_OFF; LED2_OFF; LED3_OFF;break;default:break;}break;}case UART_EVENT_TX_COMPLETE:{uart_send_complete_flag = true;break;}default:break;}
}

2. hal_entry入口函数
在 hal_entry 函数里面进行硬件初始化之后,首先打印提示信息,提醒用户从串口输入数字字符。 然后默认关闭所有 LED 灯,在 while 循环里什么都不做,等待用户的输入。

void hal_entry(void)
{/* TODO: add your own code here */LED_Init();         // LED 初始化Debug_UART4_Init(); // SCI4 UART 调试串口初始化printf("这是一个串口控制 LED 例程\r\n");printf("打开串口助手发送以下指令,控制 LED 的状态\r\n");printf ("\t指令   ------  状态\r\n ");printf ("\t 1   ------  LED1_ON\r\n ");printf ("\t 2   ------  LED2_ON\r\n ");printf ("\t 3   ------  LED3_ON\r\n ");printf ("\t 4   ------  LED1_OFF\r\n ");printf ("\t 5   ------  LED2_OFF\r\n ");printf ("\t 6   ------  LED3_OFF\r\n ");printf ("\t 7   ------  LED 全亮\r\n ");printf ("\t 8   ------  LED 全灭\r\n ");LED1_OFF; LED2_OFF; LED3_OFF;   //默认关闭所有 LED 灯while(1){}#if BSP_TZ_SECURE_BUILD/* Enter non-secure code */R_BSP_NonSecureEnter();
#endif
}

三、基于环形队列的UART收发回显

在实际项目开发中,由于有些串口不具备FIFO(如SCI1和SCI2)或FIFO的buffer比较小, 这可能会在数据处理速度小于数据接收速度的时候,导致数据的丢失。因此可以设计一个队列来避免这一问题。 在本实验中,使用环形队列来实现实验1的串口收发回显,将串口接收到的数据暂存在队列中, 待完成一次接收后再将队列中的数据全部发出去。

队列是一种特殊的线性表,只允许在队列头(head)删除元素,在队列尾(tail)添加元素。 当队列添加一个元素,队列尾向后移动,当队列删除一个元素,同样,删除一个元素,队列头向后移动,如下图。
在这里插入图片描述
由于存储空间是有限的,如果使用线性队列,删除元素后就会空出一段存储空间,这会造成很大的浪费。 因此实际上更多使用环形队列。并不是说这段存储空间是环形的,而是头指针和尾指针到达存储空间末尾后会回到存储空间起点。 因此在逻辑上这是循环的,如下图。
在这里插入图片描述
1. 环形队列的实现

#define DATA_LEN    300 //队列缓存大小typedef struct
{uint16_t head;   //头指针uint16_t tail;   //尾指针uint8_t data[DATA_LEN];  //队列数据
} Circular_queue_t;extern Circular_queue_t Circular_queue; //环形队列全局变量bool Queue_Init(Circular_queue_t *circular_queue);    //初始化队列
bool Queue_isEmpty(Circular_queue_t *circular_queue); //判断队列是否为空
bool Queue_isFull(Circular_queue_t *circular_queue);  //判断队列是否已满
bool Queue_Wirte(Circular_queue_t *circular_queue, uint8_t *string, uint16_t len); //写数据
bool Queue_Read(Circular_queue_t *circular_queue, uint8_t *string, uint16_t len);  //读数据
uint16_t Queue_HadUse(Circular_queue_t *circular_queue); //返回队列中数据的长度
uint16_t Queue_NoUse(Circular_queue_t *circular_queue);  //返回未使用数据的长度

2. 串口中断回调函数

/* 串口中断回调 */
void debug_uart4_callback (uart_callback_args_t * p_args)
{switch (p_args->event){case UART_EVENT_RX_CHAR:{/* 接收到数据后马上写入队列中 */Queue_Wirte(&Circular_queue, (uint8_t*) &p_args->data, 1);break;}case UART_EVENT_TX_COMPLETE:{uart_send_complete_flag = true;break;}default:break;}
}

3. hal_entry入口函数

void hal_entry(void)
{/* TODO: add your own code here */uint8_t Read_Buffer[DATA_LEN];uint16_t Read_Length;LED_Init();         // LED 初始化Debug_UART4_Init(); // SCI4 UART 调试串口初始化Queue_Init((Circular_queue_t*)&Circular_queue); //环形队列初始化printf("这是一个串口环形队列例程\r\n");printf("打开串口助手发送数据 5 个及以上的数据,接收窗口会打印所发送的数据\r\n");while(1){if (Queue_isEmpty(&Circular_queue) == false)  //判断队列中的数据不为空{Read_Length = Queue_HadUse(&Circular_queue);if( Read_Length >= 5)       // 如果队列中的数据大于等于5个,开始打印队列中的所有数据{printf("Read_Length=%d: ", Read_Length);memset(Read_Buffer, 0, DATA_LEN);/* 读出 Read_Length 个数据 */Queue_Read(&Circular_queue, Read_Buffer, Read_Length);printf("%s\r\n", Read_Buffer);}}R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);}#if BSP_TZ_SECURE_BUILD/* Enter non-secure code */R_BSP_NonSecureEnter();
#endif
}

http://www.ppmy.cn/news/87456.html

相关文章

立创梁山派学习笔记——GPIO输入检测

按键检测 前言按键的硬件电路BOOT选择复位按键唤醒按键GPIO输入框图软件配置寄存器简介1.端口控制寄存器(GPIOx_CTL, xA..I)2.端口上拉/下拉寄存器(GPIOx_PUD, xA..I)3.端口输入状态寄存器(GPIOx_ISTAT, xA..I&#xf…

Java 核心技术 卷I 第3 章 Java 的基本程序设计结构

第3 章 Java 的基本程序设计结构 3.1 一个简单的Java应用程序 Java区分大小写 关键字public 称为访问修饰符 (access modifier) 这些修饰符用于控制程序的其他部分对这段代码的访问级别。 关键字class表明Java程序中的全部内容都包含在类中。 类是…

自动化测试 selenium

目录 一、了解自动化测试和selenium 1. 什么是自动化测试?为什么要使用自动化测试? 2. 为什么使用selenium? 3. 环境部署 4. 什么是驱动?驱动的工作原理 5. selenium 的依赖代码 二、selenium 的基础语法 1. 元素的定位 …

Kafka实时数据即席查询应用与实践

作者:vivo 互联网搜索团队- Deng Jie Kafka中的实时数据是以Topic的概念进行分类存储,而Topic的数据是有一定时效性的,比如保存24小时、36小时、48小时等。而在定位一些实时数据的Case时,如果没有对实时数据进行历史归档&#xff…

B=800X30000可移动输送机 多向混合机设计 全自动碳清真空清洗机 QD型电动双梁桥式起重机图纸 鼓风机房详细施工图 便捷式清洗机设计 …CAD

机箱涂装生产线清洗机总图主机液压站1#中间罐车横移阀组原理图LD20电动单梁起重机详细图纸出料螺旋输送机锤片破碎机设计CAD图纸最新二代一拖二口罩外耳带机 电路图主机液压站1#-2#拉矫机阀组原理图B800X30000可移动输送机多向混合机毕业设计全自动碳清真空清洗机QD型电动双梁桥…

Bean作用域、生命周期

bean作用域如何设置行为模式Bean的生命周期 bean作用域 Bean 的作用域指bean在spring框架的某种行为模式;bean的6种作用域分别是以下: 1:单例(Singleton)作用域 2:原型(Prototype)作…

安信可(云知声蜂鸟US516P6)SDK开发学习---freertos os接口函数封装管理

安信可(云知声蜂鸟US516P6)SDK开发学习—freertos os接口函数封装管理 线程,互斥锁,、延时函数,任务优先级定义,线程栈定义 #define uni_usleep(us) vTaskDelay(((us)/1001 1) / por…

B站动态自检方法1 bilibili应用自检

文章目录 相关地址注意事项主要流程假设你的 IP:192.188.0.100端口必须设置:8899一、电脑设置代理1、查看IP2、电脑设置代理 二、手机配置代理三、安装证书四、启动动态自检方式11、点击开启动态检测2、按测试流程进行测试:3、完成自测后,点击…