黄吴久 发表于 2022-7-1 00:27

【RTT&瑞萨高性能 CPK-RA6M4 开发板 测评】- 串口和USB通信

<p>这篇接着讲讲串口和USB通信,主要讲讲串口和USB-CDC类的配置,对于串口和USB-CDC的使用就简单说两句。</p>

<p>首先是串口的配置,在打开图形化配置工具,在Pins设置中找到Peripherals-&gt;SCI,然后选中SCI0-SCI9其中一个,就可以在Pin Configuration中配置Operation Mode为Asynchronous UART模式了,也就是我们常用的全双工串口,然后可以配置TXD0和RXD0的引脚分配</p>

<p></p>

<p>&nbsp;</p>

<p>我 选择的是P101和P410这两个脚,这两个脚在评估版上的位置是挨着的,如下图</p>

<p> &nbsp;</p>

<p>选择好要使用的引脚后,我们就要给串口分配堆栈了,打开Stacks页面</p>

<p></p>

<p>点击1.New Stack,打开一个下拉列表,在其中找到Connectivity-&gt;UART(r_sci_uart),点击后就能生成2中的堆栈。</p>

<p>&nbsp; &nbsp;然后点击右上角的1处,将透视图切换到图形化配置工具界面,然后点击我们刚添加的堆栈模块,就能看到左下角这个属性配置界面。不得不吐槽下,这个编辑器的透视图切换真的不智能,都不能自己切换配置界面,不知道是不是只有我的这样。</p>

<p></p>

<p>对于这个属性的配置,暂时只用惯性下图中红框内内容就行了,1中的内容可以不用更改,2中的第一项为波特率,可以根据具体情况而定,3中的Callback后是串口回调函数的名称,写一个好识别的就行,比如我写的是user_uart_callback,到这串口的配置就算完成了。</p>

<p></p>

<p>然后来讲讲USB-CDC的配置,</p>

<p>还是一样,第一步先进行USB的时钟配置,打开Clock页面,下图中的箭头流向标注的USB时钟的来源,我们将PLL2 Src改为: HOCO,改变PLL2 Div和PLL2 Mul的值,将UCLK Src选为PLL2,改变UCLK Div的值,最终使USB的时钟UCLK为48MHz。</p>

<p> &nbsp;</p>

<p>然后是管脚配置,找到下图中的USB,将Operation Mode选择为Device,驱动模式。</p>

<p> &nbsp;</p>

<p>然后切换到Stack界面,添加USB_PCDC(r_usb_pcdc)</p>

<p></p>

<p>点击新创建的堆栈模块,在属性中可以看到关于USB的相关配置,这里不做修改。</p>

<p>然后就点击右上角的运行图标来获取更新代码。</p>

<p>串口和USB都配置完了,那么下面就讲讲代码,这此我需要实现的是通过串口发送命令给单片机,单片机点亮或熄灭LED,并通过USB将LED状态发送到虚拟串口上。</p>

<p>首先是串口,对于串口我们需要知道下面这三个函数,分别是开启串口,串口读取数据,串口发送数据。</p>

<pre>
<code>fsp_err_t R_SCI_UART_Open(uart_ctrl_t *const p_api_ctrl, uart_cfg_t const *const p_cfg)</code></pre>

<pre>
<code>fsp_err_t R_SCI_UART_Read(uart_ctrl_t *const p_api_ctrl, uint8_t *const p_dest, uint32_t const bytes)</code></pre>

<pre>
<code>fsp_err_t R_SCI_UART_Write(uart_ctrl_t *const p_api_ctrl, uint8_t const *const p_src, uint32_t const bytes)</code></pre>

<p>R_SCI_UART_Open()这个函数需要在程序运行时使用,来启用串口,我将它放在hal_entry()函数开始的位置。</p>

<p><a href="http://t.csdn.cn/z2pWo" target="_blank">对于串口的接收和发送部分我也是在网上找到的,建议可以去看看这篇博客。</a>在这篇博客中有讲如何重定向printf函数和串口如何收发数据。</p>

<p>对于我来说,我暂时只接收一定长度的数据,我将我的串口处理部分先贴出来,</p>

<p>在串口文件usart_printf.c中串口处理部分:</p>

<pre>
<code>#include &lt;stdio.h&gt;
#include "hal_data.h"
#include "usart_printf.h"

uint8_t RxBuff;      //进入中断接收数据的数组
uint8_t DataBuff; //保存接收到的数据的数组

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-&gt;event == UART_EVENT_TX_COMPLETE)
    {
      uart_send_complete_flag = true;
    }
    if(p_args-&gt;event ==   UART_EVENT_RX_CHAR)
    {
      RxBuff = (uint8_t)p_args-&gt;data;
      RxLine++;                      //每接收到一个数据,进入回调数据长度加1
      DataBuff=RxBuff;//把每次接收到的数据保存到缓存数组
      Rx_flag=1;
      if(RxBuff=='\n')            //接收结束标志位,这个数据可以自定义,根据实际需求,这里只做示例使用,不一定是0xff
      {
            Rx_flag_finish=1;
      }
      RxBuff=0;
    }

}
void printf_usart(void)
{

    printf("length=%d\r\n",RxLine);
    for(int i=0;i&lt;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;
}
</code></pre>

<p>&nbsp;串口重定向部分,</p>

<pre>
<code>#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(&amp;g_uart0_ctrl, (uint8_t *)&amp;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&lt;size;i++)
    {
      __io_putchar(*pBuffer++);
    }
    return size;
}</code></pre>

<p>串口部分的代码基本和参考的那篇博客中的一样,在user_uart_callback (uart_callback_args_t * p_args),这个函数中,我将串口接收结尾标志改为&#39;\n&#39;,这样就可以接受字符串了。</p>

<p>对于USB-CDC部分的代码参考了瑞萨的使用手册。</p>

<p>需要创建四个变量:</p>

<pre>
<code>/* 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类</code></pre>

<p>然后是USB的初始化:</p>

<pre>
<code>//CDC串口参数
    static usb_pcdc_linecoding_t g_line_coding;
    //开启USB
    R_USB_Open (&amp;g_basic0_ctrl, &amp;g_basic0_cfg);
    R_USB_ClassTypeGet (&amp;g_basic0_ctrl, &amp;g_usb_class_type);
    R_USB_ModuleNumberGet (&amp;g_basic0_ctrl, &amp;g_usb_module_number);</code></pre>

<p>由于没有使用回调函数,这里就在主函数中循环读取USB状态:</p>

<pre>
<code>      /* Obtain USB related events */
      R_USB_EventGet (&amp;g_basic0_ctrl, &amp;usb_event);</code></pre>

<p>然后解析状态:</p>

<pre>
<code>      /* USB event received by R_USB_EventGet */
      if (usb_event == USB_STATUS_REQUEST)
      {
            R_USB_SetupGet (&amp;g_basic0_ctrl, &amp;usb_setup);
            if (USB_PCDC_SET_LINE_CODING == (usb_setup.request_type &amp; USB_BREQUEST))
            {
            /* Configure virtual UART settings */
                R_USB_PeriControlDataGet (&amp;g_basic0_ctrl, (uint8_t*) &amp;g_line_coding, LINE_CODING_LENGTH);
            }
            else if (USB_PCDC_GET_LINE_CODING == (usb_setup.request_type &amp; USB_BREQUEST))
            {
            /* Send virtual UART settings back to host */
                R_USB_PeriControlDataSet (&amp;g_basic0_ctrl, (uint8_t*) &amp;g_line_coding, LINE_CODING_LENGTH);
            }
            else if (USB_PCDC_SET_CONTROL_LINE_STATE == (usb_setup.request_type &amp; USB_BREQUEST))
            {
                /* Acknowledge all other status requests */
                R_USB_PeriControlStatusSet (&amp;g_basic0_ctrl,
                USB_SETUP_STATUS_ACK);
            }
            else
            {
            }
      }</code></pre>

<p>在解析状态中可以获取到usb读到数据的状态,可以在USB接收数据是使用。我暂时不用USB接收数据,只用USB发送数据,相对简单一点。</p>

<pre>
<code>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)</code></pre>

<p>上面这个函数是USB发送数据时使用的。USB发送数据如下面所示:</p>

<pre>
<code>      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-&gt;pinWrite(&amp;g_ioport_ctrl, User_LED, led_level);
                R_USB_Write(&amp;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-&gt;pinWrite(&amp;g_ioport_ctrl, User_LED, led_level);
                R_USB_Write(&amp;g_basic0_ctrl, (uint8_t*) send_str, ((uint32_t) strlen(send_str)), (uint8_t) g_usb_class_type);
            }
            printf_usart();
      }</code></pre>

<p>在上面的代码中,我实现了对串口数据的对比,识别到控制命令&ldquo;LED on\r\n&rdquo;和&ldquo;LED off\r\n&rdquo;,然后对LED状态进行改变,并且通过USB将LED状态发送到虚拟串口。</p>

<p>完整代码包含三个文档:</p>

<p>hal_entry.c</p>

<pre>
<code>#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 = { "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(&amp;g_uart0_ctrl, &amp;g_uart0_cfg);
    //CDC串口参数
    static usb_pcdc_linecoding_t g_line_coding;
    //开启USB
    R_USB_Open (&amp;g_basic0_ctrl, &amp;g_basic0_cfg);
    R_USB_ClassTypeGet (&amp;g_basic0_ctrl, &amp;g_usb_class_type);
    R_USB_ModuleNumberGet (&amp;g_basic0_ctrl, &amp;g_usb_module_number);
    while(1){

      /* Obtain USB related events */
      R_USB_EventGet (&amp;g_basic0_ctrl, &amp;usb_event);
      /* USB event received by R_USB_EventGet */
      if (usb_event == USB_STATUS_REQUEST)
      {
            R_USB_SetupGet (&amp;g_basic0_ctrl, &amp;usb_setup);
            if (USB_PCDC_SET_LINE_CODING == (usb_setup.request_type &amp; USB_BREQUEST))
            {
            /* Configure virtual UART settings */
                R_USB_PeriControlDataGet (&amp;g_basic0_ctrl, (uint8_t*) &amp;g_line_coding, LINE_CODING_LENGTH);
            }
            else if (USB_PCDC_GET_LINE_CODING == (usb_setup.request_type &amp; USB_BREQUEST))
            {
            /* Send virtual UART settings back to host */
                R_USB_PeriControlDataSet (&amp;g_basic0_ctrl, (uint8_t*) &amp;g_line_coding, LINE_CODING_LENGTH);
            }
            else if (USB_PCDC_SET_CONTROL_LINE_STATE == (usb_setup.request_type &amp; USB_BREQUEST))
            {
                /* Acknowledge all other status requests */
                R_USB_PeriControlStatusSet (&amp;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-&gt;pinWrite(&amp;g_ioport_ctrl, User_LED, led_level);
                R_USB_Write(&amp;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-&gt;pinWrite(&amp;g_ioport_ctrl, User_LED, led_level);
                R_USB_Write(&amp;g_basic0_ctrl, (uint8_t*) send_str, ((uint32_t) strlen(send_str)), (uint8_t) g_usb_class_type);
            }
            printf_usart();
      }

//      g_ioport.p_api-&gt;pinWrite(&amp;g_ioport_ctrl, User_LED, BSP_IO_LEVEL_LOW);
//      R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
//      g_ioport.p_api-&gt;pinWrite(&amp;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.
*
* @paramevent    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-&gt;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 (&amp;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
</code></pre>

<p>usart_printf.c</p>

<pre>
<code> * usart.c
*
*Created on: 2022年6月13日
*      Author: huang
*/
#include &lt;stdio.h&gt;
#include "hal_data.h"
#include "usart_printf.h"

uint8_t RxBuff;      //进入中断接收数据的数组
uint8_t DataBuff; //保存接收到的数据的数组

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-&gt;event == UART_EVENT_TX_COMPLETE)
    {
      uart_send_complete_flag = true;
    }
    if(p_args-&gt;event ==   UART_EVENT_RX_CHAR)
    {
      RxBuff = (uint8_t)p_args-&gt;data;
      RxLine++;                      //每接收到一个数据,进入回调数据长度加1
      DataBuff=RxBuff;//把每次接收到的数据保存到缓存数组
      Rx_flag=1;
      if(RxBuff=='\n')            //接收结束标志位,这个数据可以自定义,根据实际需求,这里只做示例使用,不一定是0xff
      {
            Rx_flag_finish=1;
      }
      RxBuff=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(&amp;g_uart0_ctrl, (uint8_t *)&amp;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&lt;size;i++)
    {
      __io_putchar(*pBuffer++);
    }
    return size;
}
void printf_usart(void)
{

    printf("length=%d\r\n",RxLine);
    for(int i=0;i&lt;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;
}
</code></pre>

<p>usart_printf.h</p>

<pre>
<code> * 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;

void printf_usart(void);

void user_uart_callback (uart_callback_args_t * p_args);

#endif /* INC_USART_H_ */
</code></pre>

<p>然后又一个实验效果视频。717a014baac77de557022f8e61023a2d</p>

Jacktang 发表于 2022-7-2 07:23

<p>红绿灯已经开始闪烁,期待后续</p>

<p>&nbsp;</p>

<p> &nbsp;</p>
页: [1]
查看完整版本: 【RTT&瑞萨高性能 CPK-RA6M4 开发板 测评】- 串口和USB通信