热度 1|
这篇接着讲讲串口和USB通信,主要讲讲串口和USB-CDC类的配置,对于串口和USB-CDC的使用就简单说两句。
首先是串口的配置,在打开图形化配置工具,在Pins设置中找到Peripherals->SCI,然后选中SCI0-SCI9其中一个,就可以在Pin Configuration中配置Operation Mode为Asynchronous UART模式了,也就是我们常用的全双工串口,然后可以配置TXD0和RXD0的引脚分配
我 选择的是P101和P410这两个脚,这两个脚在评估版上的位置是挨着的,如下图
选择好要使用的引脚后,我们就要给串口分配堆栈了,打开Stacks页面
点击1.New Stack,打开一个下拉列表,在其中找到Connectivity->UART(r_sci_uart),点击后就能生成2中的堆栈。
然后点击右上角的1处,将透视图切换到图形化配置工具界面,然后点击我们刚添加的堆栈模块,就能看到左下角这个属性配置界面。不得不吐槽下,这个编辑器的透视图切换真的不智能,都不能自己切换配置界面,不知道是不是只有我的这样。
对于这个属性的配置,暂时只用惯性下图中红框内内容就行了,1中的内容可以不用更改,2中的第一项为波特率,可以根据具体情况而定,3中的Callback后是串口回调函数的名称,写一个好识别的就行,比如我写的是user_uart_callback,到这串口的配置就算完成了。
然后来讲讲USB-CDC的配置,
还是一样,第一步先进行USB的时钟配置,打开Clock页面,下图中的箭头流向标注的USB时钟的来源,我们将PLL2 Src改为: HOCO,改变PLL2 Div和PLL2 Mul的值,将UCLK Src选为PLL2,改变UCLK Div的值,最终使USB的时钟UCLK为48MHz。
然后是管脚配置,找到下图中的USB,将Operation Mode选择为Device,驱动模式。
然后切换到Stack界面,添加USB_PCDC(r_usb_pcdc)
点击新创建的堆栈模块,在属性中可以看到关于USB的相关配置,这里不做修改。
然后就点击右上角的运行图标来获取更新代码。
串口和USB都配置完了,那么下面就讲讲代码,这此我需要实现的是通过串口发送命令给单片机,单片机点亮或熄灭LED,并通过USB将LED状态发送到虚拟串口上。
首先是串口,对于串口我们需要知道下面这三个函数,分别是开启串口,串口读取数据,串口发送数据。
fsp_err_t R_SCI_UART_Open(uart_ctrl_t *const p_api_ctrl, uart_cfg_t const *const p_cfg)
fsp_err_t R_SCI_UART_Read(uart_ctrl_t *const p_api_ctrl, uint8_t *const p_dest, uint32_t const bytes)
fsp_err_t R_SCI_UART_Write(uart_ctrl_t *const p_api_ctrl, uint8_t const *const p_src, uint32_t const bytes)
R_SCI_UART_Open()这个函数需要在程序运行时使用,来启用串口,我将它放在hal_entry()函数开始的位置。
对于串口的接收和发送部分我也是在网上找到的,建议可以去看看这篇博客。在这篇博客中有讲如何重定向printf函数和串口如何收发数据。
对于我来说,我暂时只接收一定长度的数据,我将我的串口处理部分先贴出来,
在串口文件usart_printf.c中串口处理部分:
#include <stdio.h>
#include "hal_data.h"
#include "usart_printf.h"
uint8_t RxBuff[1]; //进入中断接收数据的数组
uint8_t DataBuff[500]; //保存接收到的数据的数组
int RxLine=0; //接收到的数据长度
int Rx_flag=0; //接受到数据标志
int Rx_flag_finish=0; //接受完成
fsp_err_t err = FSP_SUCCESS;
volatile bool uart_send_complete_flag = false;
void user_uart_callback (uart_callback_args_t * p_args)
{
if(p_args->event == UART_EVENT_TX_COMPLETE)
{
uart_send_complete_flag = true;
}
if(p_args->event == UART_EVENT_RX_CHAR)
{
RxBuff[0] = (uint8_t)p_args->data;
RxLine++; //每接收到一个数据,进入回调数据长度加1
DataBuff[RxLine-1]=RxBuff[0]; //把每次接收到的数据保存到缓存数组
Rx_flag=1;
if(RxBuff[0]=='\n') //接收结束标志位,这个数据可以自定义,根据实际需求,这里只做示例使用,不一定是0xff
{
Rx_flag_finish=1;
}
RxBuff[0]=0;
}
}
void printf_usart(void)
{
printf("length=%d\r\n",RxLine);
for(int i=0;i<RxLine;i++)
printf("data:[%d] = 0x%x\r\n",i,DataBuff);
memset(DataBuff,0,sizeof(DataBuff)); //清空缓存数组
//memset()作用:可以方便的清空一个结构类型的变量或数组。
//例句:memset(aTxbuffer,0,sizeof(aTxbuffer)) 用memset清空aTxbuffer。
RxLine=0; //清空接收长度
Rx_flag_finish=0;
Rx_flag = 0;
}
串口重定向部分,
#ifdef __GNUC__ //串口重定向
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
err = R_SCI_UART_Write(&g_uart0_ctrl, (uint8_t *)&ch, 1);
if(FSP_SUCCESS != err) __BKPT();
while(uart_send_complete_flag == false){}
uart_send_complete_flag = false;
return ch;
}
int _write(int fd,char *pBuffer,int size)
{
for(int i=0;i<size;i++)
{
__io_putchar(*pBuffer++);
}
return size;
}
串口部分的代码基本和参考的那篇博客中的一样,在user_uart_callback (uart_callback_args_t * p_args),这个函数中,我将串口接收结尾标志改为'\n',这样就可以接受字符串了。
对于USB-CDC部分的代码参考了瑞萨的使用手册。
需要创建四个变量:
/* Global variables for the USB */
usb_status_t usb_event; //USB状态
usb_setup_t usb_setup; //USB事件
uint8_t g_usb_module_number = 0x00; //USB模块编号
usb_class_t g_usb_class_type = 0x00; //USB类
然后是USB的初始化:
//CDC串口参数
static usb_pcdc_linecoding_t g_line_coding;
//开启USB
R_USB_Open (&g_basic0_ctrl, &g_basic0_cfg);
R_USB_ClassTypeGet (&g_basic0_ctrl, &g_usb_class_type);
R_USB_ModuleNumberGet (&g_basic0_ctrl, &g_usb_module_number);
由于没有使用回调函数,这里就在主函数中循环读取USB状态:
/* Obtain USB related events */
R_USB_EventGet (&g_basic0_ctrl, &usb_event);
然后解析状态:
/* USB event received by R_USB_EventGet */
if (usb_event == USB_STATUS_REQUEST)
{
R_USB_SetupGet (&g_basic0_ctrl, &usb_setup);
if (USB_PCDC_SET_LINE_CODING == (usb_setup.request_type & USB_BREQUEST))
{
/* Configure virtual UART settings */
R_USB_PeriControlDataGet (&g_basic0_ctrl, (uint8_t*) &g_line_coding, LINE_CODING_LENGTH);
}
else if (USB_PCDC_GET_LINE_CODING == (usb_setup.request_type & USB_BREQUEST))
{
/* Send virtual UART settings back to host */
R_USB_PeriControlDataSet (&g_basic0_ctrl, (uint8_t*) &g_line_coding, LINE_CODING_LENGTH);
}
else if (USB_PCDC_SET_CONTROL_LINE_STATE == (usb_setup.request_type & USB_BREQUEST))
{
/* Acknowledge all other status requests */
R_USB_PeriControlStatusSet (&g_basic0_ctrl,
USB_SETUP_STATUS_ACK);
}
else
{
}
}
在解析状态中可以获取到usb读到数据的状态,可以在USB接收数据是使用。我暂时不用USB接收数据,只用USB发送数据,相对简单一点。
fsp_err_t R_USB_Write(usb_ctrl_t *const p_api_ctrl, uint8_t const *const p_buf, uint32_t size, uint8_t destination)
上面这个函数是USB发送数据时使用的。USB发送数据如下面所示:
if(Rx_flag_finish == 1){
Rx_flag_finish = 0;
printf("%s", DataBuff);
if (memcmp(DataBuff, "LED on\r\n", 8) == 0){
strcpy (send_str, "LED is on\r\n");
led_level = BSP_IO_LEVEL_HIGH;
g_ioport.p_api->pinWrite(&g_ioport_ctrl, User_LED, led_level);
R_USB_Write(&g_basic0_ctrl, (uint8_t*) send_str, ((uint32_t) strlen(send_str)), (uint8_t) g_usb_class_type);
}
else if(memcmp(DataBuff, "LED off\r\n", 8) == 0){
strcpy (send_str, "LED is off\r\n");
led_level = BSP_IO_LEVEL_LOW;
g_ioport.p_api->pinWrite(&g_ioport_ctrl, User_LED, led_level);
R_USB_Write(&g_basic0_ctrl, (uint8_t*) send_str, ((uint32_t) strlen(send_str)), (uint8_t) g_usb_class_type);
}
printf_usart();
}
在上面的代码中,我实现了对串口数据的对比,识别到控制命令“LED on\r\n”和“LED off\r\n”,然后对LED状态进行改变,并且通过USB将LED状态发送到虚拟串口。
完整代码包含三个文档:
hal_entry.c
#include "hal_data.h"
#include "usart_printf.h"
#define LINE_CODING_LENGTH (0x07U)
FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER
/* Global variables for the USB */
usb_status_t usb_event; //USB状态
usb_setup_t usb_setup; //USB事件
uint8_t g_usb_module_number = 0x00; //USB模块编号
usb_class_t g_usb_class_type = 0x00; //USB类
/* Global variables for the program */
static char send_str[20] = { "LED on\r\n" }; //发送字符串
static volatile uint8_t sw1_pressed = false; //触发转换标志
static uint8_t led_level = BSP_IO_LEVEL_HIGH; //LED状态
/*******************************************************************************************************************//**
* 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 */
//开启串口
R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
//CDC串口参数
static usb_pcdc_linecoding_t g_line_coding;
//开启USB
R_USB_Open (&g_basic0_ctrl, &g_basic0_cfg);
R_USB_ClassTypeGet (&g_basic0_ctrl, &g_usb_class_type);
R_USB_ModuleNumberGet (&g_basic0_ctrl, &g_usb_module_number);
while(1){
/* Obtain USB related events */
R_USB_EventGet (&g_basic0_ctrl, &usb_event);
/* USB event received by R_USB_EventGet */
if (usb_event == USB_STATUS_REQUEST)
{
R_USB_SetupGet (&g_basic0_ctrl, &usb_setup);
if (USB_PCDC_SET_LINE_CODING == (usb_setup.request_type & USB_BREQUEST))
{
/* Configure virtual UART settings */
R_USB_PeriControlDataGet (&g_basic0_ctrl, (uint8_t*) &g_line_coding, LINE_CODING_LENGTH);
}
else if (USB_PCDC_GET_LINE_CODING == (usb_setup.request_type & USB_BREQUEST))
{
/* Send virtual UART settings back to host */
R_USB_PeriControlDataSet (&g_basic0_ctrl, (uint8_t*) &g_line_coding, LINE_CODING_LENGTH);
}
else if (USB_PCDC_SET_CONTROL_LINE_STATE == (usb_setup.request_type & USB_BREQUEST))
{
/* Acknowledge all other status requests */
R_USB_PeriControlStatusSet (&g_basic0_ctrl,
USB_SETUP_STATUS_ACK);
}
else
{
}
}
if(Rx_flag_finish == 1){
Rx_flag_finish = 0;
printf("%s", DataBuff);
if (memcmp(DataBuff, "LED on\r\n", 8) == 0){
strcpy (send_str, "LED is on\r\n");
led_level = BSP_IO_LEVEL_HIGH;
g_ioport.p_api->pinWrite(&g_ioport_ctrl, User_LED, led_level);
R_USB_Write(&g_basic0_ctrl, (uint8_t*) send_str, ((uint32_t) strlen(send_str)), (uint8_t) g_usb_class_type);
}
else if(memcmp(DataBuff, "LED off\r\n", 8) == 0){
strcpy (send_str, "LED is off\r\n");
led_level = BSP_IO_LEVEL_LOW;
g_ioport.p_api->pinWrite(&g_ioport_ctrl, User_LED, led_level);
R_USB_Write(&g_basic0_ctrl, (uint8_t*) send_str, ((uint32_t) strlen(send_str)), (uint8_t) g_usb_class_type);
}
printf_usart();
}
// g_ioport.p_api->pinWrite(&g_ioport_ctrl, User_LED, BSP_IO_LEVEL_LOW);
// R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
// g_ioport.p_api->pinWrite(&g_ioport_ctrl, User_LED, BSP_IO_LEVEL_HIGH);
// R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
/*******************************************************************************************************************//**
* This function is called at various points during the startup process. This implementation uses the event that is
* called right before main() to set up the pins.
*
* @param[in] event Where at in the start up process the code is currently at
**********************************************************************************************************************/
void R_BSP_WarmStart(bsp_warm_start_event_t event)
{
if (BSP_WARM_START_RESET == event)
{
#if BSP_FEATURE_FLASH_LP_VERSION != 0
/* Enable reading from data flash. */
R_FACI_LP->DFLCTL = 1U;
/* Would normally have to wait tDSTOP(6us) for data flash recovery. Placing the enable here, before clock and
* C runtime initialization, should negate the need for a delay since the initialization will typically take more than 6us. */
#endif
}
if (BSP_WARM_START_POST_C == event)
{
/* C runtime environment and system clocks are setup. */
/* Configure pins. */
R_IOPORT_Open (&g_ioport_ctrl, g_ioport.p_cfg);
}
}
#if BSP_TZ_SECURE_BUILD
BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ();
/* Trustzone Secure Projects require at least one nonsecure callable function in order to build (Remove this if it is not required to build). */
BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ()
{
}
#endif
usart_printf.c
* usart.c
*
* Created on: 2022年6月13日
* Author: huang
*/
#include <stdio.h>
#include "hal_data.h"
#include "usart_printf.h"
uint8_t RxBuff[1]; //进入中断接收数据的数组
uint8_t DataBuff[500]; //保存接收到的数据的数组
int RxLine=0; //接收到的数据长度
int Rx_flag=0; //接受到数据标志
int Rx_flag_finish=0; //接受完成
fsp_err_t err = FSP_SUCCESS;
volatile bool uart_send_complete_flag = false;
void user_uart_callback (uart_callback_args_t * p_args)
{
if(p_args->event == UART_EVENT_TX_COMPLETE)
{
uart_send_complete_flag = true;
}
if(p_args->event == UART_EVENT_RX_CHAR)
{
RxBuff[0] = (uint8_t)p_args->data;
RxLine++; //每接收到一个数据,进入回调数据长度加1
DataBuff[RxLine-1]=RxBuff[0]; //把每次接收到的数据保存到缓存数组
Rx_flag=1;
if(RxBuff[0]=='\n') //接收结束标志位,这个数据可以自定义,根据实际需求,这里只做示例使用,不一定是0xff
{
Rx_flag_finish=1;
}
RxBuff[0]=0;
}
}
#ifdef __GNUC__ //串口重定向
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
err = R_SCI_UART_Write(&g_uart0_ctrl, (uint8_t *)&ch, 1);
if(FSP_SUCCESS != err) __BKPT();
while(uart_send_complete_flag == false){}
uart_send_complete_flag = false;
return ch;
}
int _write(int fd,char *pBuffer,int size)
{
for(int i=0;i<size;i++)
{
__io_putchar(*pBuffer++);
}
return size;
}
void printf_usart(void)
{
printf("length=%d\r\n",RxLine);
for(int i=0;i<RxLine;i++)
printf("data:[%d] = 0x%x\r\n",i,DataBuff);
memset(DataBuff,0,sizeof(DataBuff)); //清空缓存数组
//memset()作用:可以方便的清空一个结构类型的变量或数组。
//例句:memset(aTxbuffer,0,sizeof(aTxbuffer)) 用memset清空aTxbuffer。
RxLine=0; //清空接收长度
Rx_flag_finish=0;
Rx_flag = 0;
}
usart_printf.h
* usart.h
*
* Created on: 2022年6月13日
* Author: huang
*/
#ifndef INC_USART_H_
#define INC_USART_H_
extern int Rx_flag_finish; //接受完成
extern uint8_t DataBuff[500];
void printf_usart(void);
void user_uart_callback (uart_callback_args_t * p_args);
#endif /* INC_USART_H_ */
然后又一个实验效果视频。