野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 632|回复: 0

瑞萨RA MCU创意氛围赛+基于瑞萨RA6M5的信号处理工具集

[复制链接]
发表于 2023-8-19 23:59:19 | 显示全部楼层 |阅读模式
本帖最后由 qinyunti 于 2023-8-20 00:01 编辑

.前言
本板基于瑞萨RA6 系列 高性能MCU:基于支持 TrustZone Arm Cortex-M33F 内核或 Arm Cortex-M4F 内核。最高频 率 200 MHz。高达 2 MB 的闪存和 640 KB SRAM。外设包括数据转换 器、定时器、外部存储总线、以太网、全速和高速 USBCAN、安全功能、电容式触摸感应和用于 TFT 显示的图形 LCD 控制器,以及一个 2D 图形引擎。
本文主要基于ADC进行信号采集,DSP算法库进行信号分析,实现信号处理分析前端。
基于此实现各种信号分析的应用场景,比如电能质量分析,噪声分析,虚拟示波器,滤波器等等,All In One并且方便扩展更多的应用,是一个瑞士军刀类型的工具集。并且设计了命令行交互等,可以和上位机进行交互,可视化,或者供其他主机调用。
本文尽可能详细的介绍整个实现过程。

框图:
图片1.png
演示视频:
<iframe src="//player.bilibili.com/player.html?bvid=BV1zu4y1Q7Mm&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
https://www.bilibili.com/video/BV1zu4y1Q7Mm/
源码地址:
https://gitee.com/qinyunti/ra6-m5.git
.准备2.1安装RASCMDK
参考相关资料,这里不再赘述

2.2芯片手册
RA6M5 Group User's Manual: Hardware
RA6M5 Group Datasheet
2.3熟悉原理图
原理图等资料可以如下地址下载
https://doc.embedfire.com/products/link/zh/latest/mcu/renesas/ebf_ra.html
这里对基本的接口熟悉,其他模块用到再去看原理图。
时钟
提供了24MHz的外部晶体,接P212P213可以使用其作为系统时钟源
图片2.png
调试串口
使用CH340G实现USB转串口接P511P512
图片3.png
使用USB转串口接P511P512需要短接J351234.

J34短接1324即使用P602P601WIFI
如果短接3546WIFIUSB转串口,可以直接PC上调试WIFI
图片4.png
仿真与BOOT接口
全引脚JTAG可以接20PJTAG接口,SWD可以使用J4,我这里使用SWD
另外J17默认短接12为单芯片运行模式,短接23则为SCI/USB BOOT模式。
图片5.png
LED
三个LEDP400 P403 P404
图片6.png
.创建工程3.1创建工程
打开RASC
指定工程目录,设置工程名->Next
图片7.png
选择芯片型号R7FA6M5BH3CFC和开发环境MDK
图片8.png
选择Flat开发模式
图片9.png
选择OS,我这里选择无OS
图片10.png
图片11.png

3.2配置BSP
时钟配置
前面我们看到原理图提供了24MHz外部晶体我们就使用该晶体做欸时钟源,通过PLL得到系统时钟,如下所示
图片12.png
配置LEDIO
P400 P403 P404
图片13.png
图片14.png
图片15.png
生成工程
图片16.png
3.3MDK配置
使用MDK打开test.uvprojx
先编译没问题
图片17.png
右键点击Target1->Options For Target Target 1
选择仿真器CMSIS_DAP然后点击Settings
图片18.png
如下识别到芯片
图片19.png

添加如下3个烧录算法
图片20.png
并按照手册说明设置RAM开始地址为0x20000000大小可以设置大一点,
仿真器先会将下载程序导入到该处运行

图片21.png
图片22.png
勾选如下位置,以debug时下载程序
图片23.png
勾选如下位置,仿真时停在main函数处
图片24.png


.LED控制4.1代码
我们可以看到配置的P400 P403 P404pin_data.c中如下
图片25.png

并且bsp_pin_cfg.h中根据我们之前配置的LED1 LED2 LED3的名字定义了宏
图片26.png

R_BSP_WarmStart-> R_IOPORT_Open(&g_ioport_ctrl, g_ioport.p_cfg);->会根据上述表格进行IO初始化。

所以我们无需再初始化上述IO,直接
hal_entry.chal_entry函数中添加如下控制代码即可。
         R_IOPORT_PinWrite(&g_ioport_ctrl, LED1, BSP_IO_LEVEL_LOW); //LED1
         R_IOPORT_PinWrite(&g_ioport_ctrl, LED2, BSP_IO_LEVEL_LOW); //LED2
         R_IOPORT_PinWrite(&g_ioport_ctrl, LED3, BSP_IO_LEVEL_LOW); //LED3
         R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1
         R_IOPORT_PinWrite(&g_ioport_ctrl, LED1, BSP_IO_LEVEL_HIGH); //LED1
         R_IOPORT_PinWrite(&g_ioport_ctrl, LED2, BSP_IO_LEVEL_HIGH); //LED2
         R_IOPORT_PinWrite(&g_ioport_ctrl, LED3, BSP_IO_LEVEL_HIGH); //LED3
         R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1

修改代码后再次编译代码。

4.2仿真调试
点击如下图标仿真并运行到main函数处
图片27.png
菜单Debug->run运行程序,看到LED LED2 LED3闪烁。

五. 串口收发
串口是后面调试,和主机通讯的基础,所以先实现串口收发。
根据前面的原理图可知,USB转串口接的是P511 P512
对应RXD4 TXD4
图片28.png
我们重新打开RASC配置串口
图片29.png

配置SCI4如下
图片30.png

添加串口stack
图片31.png
属性设置名字,通道和回调函数
图片32.png

设置堆栈大小
图片33.png

生成工程
图片34.png

添加uart.cuart.h文件
uart.c实现串口初始化与回调函数以及发送函数
#include "hal_data.h"
volatile static int send_flag = 0;

void uart_init(void)
{
   fsp_err_t err = FSP_SUCCESS;

   err = R_SCI_UART_Open (&g_uart4_ctrl, &g_uart4_cfg);
   assert(FSP_SUCCESS == err);
}


void uart_write(uint8_t* buffer, uint32_t len)
{
   fsp_err_t err = FSP_SUCCESS;
         send_flag = 0;
         err = R_SCI_UART_Write(&g_uart4_ctrl, buffer, len);
        while(send_flag==0);
}

void uart_callback (uart_callback_args_t * p_args)
{
         uint8_t val = 0;
   switch (p_args->event)
   {
      case UART_EVENT_RX_CHAR:
      {
            /* 把串口接收到的数据发送回去 */
                                          val = *(uint8_t *)&(p_args->data);
            //R_SCI_UART_Write(&g_uart4_ctrl, (uint8_t *)&(p_args->data), 1);
                                    uart_write(&val, 1);
            break;
      }
      case UART_EVENT_TX_COMPLETE:
      {
send_flag = 1;
            break;
      }
      default:
            break;
   }
}

Uart.h函数接口
#ifndef UART_H
#define UART_H

void uart_init(void);

#endif

hal_entry.c
#include "uart.h"
然后调用初始化
uart_init();

使用串口调试助手测试
115200-8-n-1,发送数据原样返回
串口收发OK
图片35.png
.串口驱动
为了方便应用层使用,我们基于前面的串口收发,实现基于缓冲区的串口驱动,提供串口收发接口。
基本实现思路如下
设计环形缓冲区用于接收数据,
串口中断中将接收的数据写入接收缓冲区,如果满则丢弃,接收应用接口则查询该接收缓冲区如果缓冲区中数据不够则等待直到超时,收到多少就返回多少。
以上接收中断和应用接口都需要操作该缓冲区所以需要临界段处理。
图片36.png
6.1环形缓冲区数据结构
typedef struct
{
        uint32_t datalen_u32;
        uint32_t maxlen_u32;
        uint32_t in_u32;
        uint32_t out_u32;
        uint8_t* buffer_pu8;
}ring_buffer_t;


uint8_t uart_ring_buffer[128];

ring_buffer_t s_ring_buffer_t=
{
                .datalen_u32 = 0,
                .maxlen_u32 = sizeof(uart_ring_buffer),
                .in_u32 = 0,
                .out_u32 = 0,
                .buffer_pu8 = uart_ring_buffer,
};

6.2临界段处理
缓冲区在中断和API中都需要操作,所以需要临界段保护,
采用简单的开关中断方式。

#define Enter_Critical() __disable_irq
#define Exit_Critical() __enable_irq

Enter_Critical();
临界段代码
Exit_Critical() ;
6.3串口接收中断
调用uart_rx_handler进行处理
void uart_callback (uart_callback_args_t * p_args)
{
         uint8_t val = 0;
   switch (p_args->event)
   {
      case UART_EVENT_RX_CHAR:
      {
            /* 把串口接收到的数据发送回去 */
                                          val = *(uint8_t *)&(p_args->data);
            //R_SCI_UART_Write(&g_uart4_ctrl, (uint8_t *)&(p_args->data), 1);
                                    //uart_write(&val, 1);
                                                uart_rx_handler(&val, 1);
            break;
      }
      case UART_EVENT_TX_COMPLETE:
      {
send_flag = 1;
            break;
      }
      default:
            break;
   }
}

void uart_rx_handler(const uint8_t *buffer, uint32_t length)
{
    uint32_t i;
    for(i=0;i<length; i++)
    {
        if(s_ring_buffer_t.datalen_u32 < s_ring_buffer_t.maxlen_u32)
        {
            s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.in_u32] = buffer;
            s_ring_buffer_t.datalen_u32++;
            s_ring_buffer_t.in_u32++;
            s_ring_buffer_t.in_u32 %= s_ring_buffer_t.maxlen_u32;
        }
        else
        {
            /* full */
            break;
        }
    }
}

6.4实现发送字节接口
uart.c
void uart_sendbyte(uint8_t ch)
{
        uint8_t val = ch;
        uart_write(&val, 1);
}
6.5发送API
int drv_uart_write(uint8_t *buff, uint32_t len)
{
                uint32_t i;
                for(i=0; i<len ;i++)
                {
                        uart_sendbyte(buff);
                }
                            return 0;
}

6.6接收API
uint32_t drv_uart_read(uint8_t *buff, uint32_t len)
{
    uint32_t readlen = 0;
    //uint32_t mask;
    if(s_ring_buffer_t.datalen_u32 == 0)
    {
        return 0;
    }
    Enter_Critical();
                uint32_t i;
    for(i=0;i<len;i++)
    {
                                if(s_ring_buffer_t.datalen_u32 > 0)
                                {
                                                buff = s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.out_u32];
                                                s_ring_buffer_t.datalen_u32--;
                                                s_ring_buffer_t.out_u32++;
                                                s_ring_buffer_t.out_u32 %= s_ring_buffer_t.maxlen_u32;
                                                readlen++;
                                }
                                else
                                {
                                                break;
                                }
    }
    Exit_Critical();
    return readlen;
}

6.7串口初始化
使用之前的初始化
void uart_init(void)
{
   fsp_err_t err = FSP_SUCCESS;

   err = R_SCI_UART_Open (&g_uart4_ctrl, &g_uart4_cfg);
   assert(FSP_SUCCESS == err);
}

6.8代码
drv_uart.c
#include <stdio.h>
#include <stdint.h>

#include "drv_uart.h"

typedef struct
{
        uint32_t datalen_u32;
        uint32_t maxlen_u32;
        uint32_t in_u32;
        uint32_t out_u32;
        uint8_t* buffer_pu8;
}ring_buffer_t;


uint8_t uart_ring_buffer[128];

ring_buffer_t s_ring_buffer_t=
{
                .datalen_u32 = 0,
                .maxlen_u32 = sizeof(uart_ring_buffer),
                .in_u32 = 0,
                .out_u32 = 0,
                .buffer_pu8 = uart_ring_buffer,
};

void uart_rx_handler(const uint8_t *buffer, uint32_t length)
{
    uint32_t i;
    for(i=0;i<length; i++)
    {
        if(s_ring_buffer_t.datalen_u32 < s_ring_buffer_t.maxlen_u32)
        {
            s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.in_u32] = buffer;
            s_ring_buffer_t.datalen_u32++;
            s_ring_buffer_t.in_u32++;
            s_ring_buffer_t.in_u32 %= s_ring_buffer_t.maxlen_u32;
        }
        else
        {
            /* full */
            break;
        }
    }
}

uint32_t drv_uart_read(uint8_t *buff, uint32_t len)
{
    uint32_t readlen = 0;
    //uint32_t mask;
    if(s_ring_buffer_t.datalen_u32 == 0)
    {
        return 0;
    }
    Enter_Critical();
                uint32_t i;
    for(i=0;i<len;i++)
    {
                                if(s_ring_buffer_t.datalen_u32 > 0)
                                {
                                                buff = s_ring_buffer_t.buffer_pu8[s_ring_buffer_t.out_u32];
                                                s_ring_buffer_t.datalen_u32--;
                                                s_ring_buffer_t.out_u32++;
                                                s_ring_buffer_t.out_u32 %= s_ring_buffer_t.maxlen_u32;
                                                readlen++;
                                }
                                else
                                {
                                                break;
                                }
    }
    Exit_Critical();
    return readlen;
}
int drv_uart_write(uint8_t *buff, uint32_t len)
{
                uint32_t i;
                for(i=0; i<len ;i++)
                {
                        uart_sendbyte(buff);
                }
                            return 0;
}

void drv_uart_init(uint32_t baud)
{
        (void)baud;
}



drv_uart.h
#ifndef DRV_UART_H
#define DRV_UART_H

#include <stdint.h>
#include "cmsis_armclang.h"

void drv_uart_init(uint32_t baud);
uint32_t drv_uart_read(uint8_t *buff, uint32_t len);
int drv_uart_write(uint8_t *buff, uint32_t len);
void uart_rx_handler(const uint8_t *buffer, uint32_t length);
extern void uart_sendbyte(uint8_t ch);

#define Enter_Critical() __disable_irq
#define Exit_Critical() __enable_irq

#endif

6.9测试
#include "drv_uart.h"

                                 uint8_t buffer[128];
                                 for(;;)
                                 {
                                                uint32_t len=0;
                                                if((len = drv_uart_read(buffer, sizeof(buffer))) >0)
                                                {
                                                  drv_uart_write(buffer, len);
                                                }
                                 }
上位机串口调试助手不断发送数据,接收到和发送的完全一致,说明收发OK
图片37.png

可以调整查询间隔和缓冲区大小来适应不同的传输速度。

.重定向标准输入输出
以上实现了串口驱动,我们现在来实现串口重定向标准输入输出以方便后面的调试,处于资源和效率考虑,我们这里移植小型的xprintf而不是使用printf
添加xprintf.c xprintf.h到工程src
xprintf.h中使能以下宏
#define XF_USE_OUTPUT        1        /* 1: Enable output functions */
#define        XF_CRLF                        1        /* 1: Convert \n ==> \r\n in the output char */
#define        XF_USE_DUMP                1        /* 1: Enable put_dump function */
#define        XF_USE_LLI                1        /* 1: Enable long long integer in size prefix ll */
#define        XF_USE_FP                1        /* 1: Enable support for floating point in type e and f */
#define XF_DPC                        '.'        /* Decimal separator for floating point */
#define XF_USE_INPUT        1        /* 1: Enable input functions */
#define        XF_INPUT_ECHO        1        /* 1: Echo back input chars in xgets function */

hal_entry.c#include "xprintf.h"
设置收发接口
void xprintf_output(int ch)
{
        uint8_t val = (uint8_t)ch;
        drv_uart_write(&val,1);
}

int xprintf_input(void)
{
        uint8_t val;
        while(drv_uart_read(&val,1) <= 0);
        return (int)val;
}

          xdev_out(xprintf_output);
          xdev_in(xprintf_input);

测试
                        xprintf("%d\n", 1234);             /* "1234" */
                        xprintf("%6d,%3d%%\n", -200, 5);   /* "  -200,  5%" */
                        xprintf("%-6u\n", 100);            /* "100   " */
                        xprintf("%ld\n", 12345678);        /* "12345678" */
                        xprintf("%llu\n", 0x100000000);    /* "4294967296"   <XF_USE_LLI> */
                        xprintf("%lld\n", -1LL);           /* "-1"           <XF_USE_LLI> */
                        xprintf("%04x\n", 0xA3);           /* "00a3" */
                        xprintf("%08lX\n", 0x123ABC);      /* "00123ABC" */
                        xprintf("%016b\n", 0x550F);        /* "0101010100001111" */
                        xprintf("%*d\n", 6, 100);          /* "   100" */
                        xprintf("%s\n", "abcdefg");        /* "abcdefg" */
                        xprintf("%5s\n", "abc");           /* "  abc" */
                        xprintf("%-5s\n", "abc");          /* "abc  " */
                        xprintf("%.5s\n", "abcdefg");      /* "abcde" */
                        xprintf("%-5.2s\n", "abcdefg");    /* "ab   " */
                        xprintf("%c\n", 'a');              /* "a" */
                        xprintf("%12f\n", 10.0);           /* "   10.000000" <XF_USE_FP> */
                        xprintf("%.4E\n", 123.45678);      /* "1.2346E+02"   <XF_USE_FP> */
                        for(;;)
                        {
                                char buffer[64];
                                char* p = buffer;
                                long a;
                                long b;
                                long c;
                                xprintf("please input int a and int b\n");
                                xgets(buffer,sizeof(buffer));
                                xatoi(&p,&a);
                                xatoi(&p,&b);
                                c = a + b;
                                xprintf("%d + %d = %d\n",a,b,c);
                        }
输入
1空格2回车
打印1 + 2 = 3
图片38.png
.命令行实现
以上实现了串口重定向,我们现在实现简单的命令行,以便后面进行交互操作。
设计思想是,定义命令字符串和实现函数的对应表,标准输入读一行,搜索对应表和字符串匹配,匹配则执行对应的函数。
图片39.png
实现代码见shell.c shell.h shell_func.c shell_func.h

测试
输入help回车打印如下

图片40.png

九. ADC采集音频
使用如下麦克风采集模块
ADC采集信号。
图片41.png
参考电压为3.3V
图片42.png

配置ADC
配置P001ADCCH1
图片43.png
添加相关代码
图片44.png
配置属性
图片45.png

图片46.png
图片47.png
生成工程
图片48.png

添加adc.cadc.h代码


测试
#include "adc.h"
初始化
                adc_init();
                static uint16_t adcbuffer[1024] = {0};
循环调用
                        adc_read(adcbuffer, 1024);
                        for(uint32_t i=0; i<1024; i++)
                        {
                                xprintf("/*%d*/\r\n",adcbuffer);
                        }
图片49.png

也可以添加命令行采集

Shell_func.h
void ADCFun(void* param);

Shell_func.c
#include "adc.h"
  { (const uint8_t*)"adc",         ADCFun,         "adc"},                    /*ADC采集*/
十. ADC可视化(虚拟示波器)
通过串口将采集的数据发送到PCPC端使用可视化上位机进行可视化,即实现了虚拟示波器的应用。
图片50.png
图片51.png
十一.DSP算法库
添加DSP算法库
使用的是CMSIS-DSP的算法库
图片52.png

添加的代码如下
图片53.png
十二.FFT谐波分析(电能质量分析)12.1FFT算法
我们可以使用fft算法对原始数据尽心分析,得到谐波,直流量
,相位,频率,幅值等信息,以进行电能质量分析,噪声分析等各种应用。

12.2添加命令行
添加命令行参数
Shell_func.h
void FftFun(void* param);

Shell_func.c
#include "fft.h"
  { (const uint8_t*)"ft",         FftFun,         "fft"},                    /*fft分析*/

void FftFun(void* param)
{
        int num;
        if(1 == sscanf(param,"%*s %d",&num))
  {
                while(num--)
    {
                        fft_test();
                }
        }
}

12.3代码
Fft.c fft.hgit
12.4测试
上位机输入fft 10
即进行10次采样分析
效果如下
图片54.png
十三.噪声检测与分析
我们也可以将开发板打造为噪声监测分析仪,对于噪声首先关心的就是其大小,我们可以实时采集声音并通过算法检测出极大值,最终换算成相对基准功率的噪声分贝值。同时也可以通过命令行控制何时进行采样分析,和其他功能是独立的,可以单独调用,集成在一起作为工具集供调用。当然也可以进行噪声的谐波等分析,和前面的点那个质量谐波分析等一样。
13.1极大值检测算法
极大值检测算法参见https://www.mdpi.com/1999-4893/5/4/588/htm
13.2添加命令行
添加命令行参数
Shell_func.h
void MaxFun(void* param);

Shell_func.c
#include "max.h"
  { (const uint8_t*)"max",         MaxFun,         "max"},                    /*极值检测*/

void MaxFun(void* param)
{
        int num;
        if(1 == sscanf(param,"%*s %d",&num))
  {
                while(num--)
    {
                        max_test();
                }
        }
}

13.3代码
Max.c max.h详见git
13.4测试
上位机输入max 10
即进行10次采样分析
可视化显示如下,黄色线是极大值检测结果,蓝色线是原始数据。

图片55.png


十四.数字滤波器14.1IIR滤波器
DSP算法库中提供了很多滤波算法,我们这里将IIR添加到我们的工具集中进行演示。


14.2添加命令行
添加命令行参数
Shell_func.h
void IirFun(void* param);

Shell_func.c
#include "iir.h"
  { (const uint8_t*)"iir",         IirFun,         "iir"},                    /*滤波分析*/

void IirFun(void* param)
{
        int num;
        if(1 == sscanf(param,"%*s %d",&num))
  {
                while(num--)
    {
                        iir_test();
                }
        }
}


14.3代码
Iir.c iir.h详见git
14.4测试
上位机输入iir 10
即进行10次采样分析
可视化显示如下,黄色线滤波结果,蓝色线是原始数据。
图片56.png
十五.相位频率幅值分析(频率计等)15.1相位分析
FFT计算结果,幅值最大的横坐标对应信号频率,纵坐标对应幅度。幅值最大的为out[m]=val;则信号频率f0=Fs/N)m ,信号幅值Vpp=val/N/2)。NFFT的点数,Fs为采样频率。相位Pha=atan2(a, b)弧度制,其中ab是输出虚数结果的实部和虚部。

15.2添加命令行
添加命令行参数
Shell_func.h
Void FrqFun(void* param);

Shell_func.c
#include "frq.h"
  { (const uint8_t*)"frq",         FrqFun,         "frq"},                    /*相位分析*/

void FrqFun(void* param)
{
        int num;
        if(1 == sscanf(param,"%*s %d",&num))
  {
                while(num--)
    {
                        Frq_test();
                }
        }
}


15.3代码
Frq.c frq.h详见git
15.4测试
上位机输入frq 10
即进行10次采样分析
图片57.png

十六. 总结
本开发板性能资源比较丰富,特别适合开发和验证等。本文基于该开发板实现了信号处理前端,实现了虚拟示波器,噪声分析仪,电能质量分析仪,数字滤波器等功能的集合,并且可以方便的快速添加更多的应用。可以基于CLI调用方便脚本化使用,可以使用上位机可视化,是一个瑞士军刀类型的工具集。目前支持的CLI命令如下,可以快速扩展更多应用。
图片58.png



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-5-2 12:07 , Processed in 0.043817 second(s), 28 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表