本帖最后由 qinyunti 于 2023-8-20 00:01 编辑
一.前言本板基于瑞萨RA6 系列 – 高性能MCU:基于支持 TrustZone 的 Arm Cortex-M33F 内核或 Arm Cortex-M4F 内核。最高频 率 200 MHz。高达 2 MB 的闪存和 640 KB 的 SRAM。外设包括数据转换 器、定时器、外部存储总线、以太网、全速和高速 USB、CAN、安全功能、电容式触摸感应和用于 TFT 显示的图形 LCD 控制器,以及一个 2D 图形引擎。 本文主要基于ADC进行信号采集,DSP算法库进行信号分析,实现信号处理分析前端。 基于此实现各种信号分析的应用场景,比如电能质量分析,噪声分析,虚拟示波器,滤波器等等,All In One并且方便扩展更多的应用,是一个瑞士军刀类型的工具集。并且设计了命令行交互等,可以和上位机进行交互,可视化,或者供其他主机调用。 本文尽可能详细的介绍整个实现过程。
框图: 演示视频: <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安装RASC和MDK参考相关资料,这里不再赘述
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的外部晶体,接P212和P213可以使用其作为系统时钟源 调试串口 使用CH340G实现USB转串口接P511,P512 使用USB转串口接P511和P512需要短接J35的12和34.
J34短接13和24即使用P602和P601接WIFI, 如果短接35和46则WIFI接USB转串口,可以直接PC上调试WIFI。 仿真与BOOT接口 全引脚JTAG可以接20PJTAG接口,SWD可以使用J4,我这里使用SWD。 另外J17默认短接12为单芯片运行模式,短接23则为SCI/USB BOOT模式。 LED 三个LED接P400 P403 P404 三.创建工程3.1创建工程打开RASC 指定工程目录,设置工程名->Next 选择芯片型号R7FA6M5BH3CFC和开发环境MDK 选择Flat开发模式 选择OS,我这里选择无OS
3.2配置BSP时钟配置 前面我们看到原理图提供了24MHz外部晶体我们就使用该晶体做欸时钟源,通过PLL得到系统时钟,如下所示 配置LED的IO P400 P403 P404 生成工程 3.3MDK配置使用MDK打开test.uvprojx 先编译没问题 右键点击Target1->Options For Target ‘Target 1’ 选择仿真器CMSIS_DAP然后点击Settings 如下识别到芯片
添加如下3个烧录算法 并按照手册说明设置RAM开始地址为0x20000000大小可以设置大一点, 仿真器先会将下载程序导入到该处运行
勾选如下位置,以debug时下载程序 勾选如下位置,仿真时停在main函数处
四.LED控制4.1代码我们可以看到配置的P400 P403 P404在pin_data.c中如下
并且bsp_pin_cfg.h中根据我们之前配置的LED1 LED2 LED3的名字定义了宏
R_BSP_WarmStart-> R_IOPORT_Open(&g_ioport_ctrl, g_ioport.p_cfg);->会根据上述表格进行IO初始化。
所以我们无需再初始化上述IO,直接 hal_entry.c中hal_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函数处 菜单Debug->run运行程序,看到LED LED2 LED3闪烁。
五. 串口收发串口是后面调试,和主机通讯的基础,所以先实现串口收发。 根据前面的原理图可知,USB转串口接的是P511 P512 对应RXD4 TXD4 我们重新打开RASC配置串口
配置SCI4如下
添加串口stack 属性设置名字,通道和回调函数
设置堆栈大小
生成工程
添加uart.c和uart.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 六.串口驱动为了方便应用层使用,我们基于前面的串口收发,实现基于缓冲区的串口驱动,提供串口收发接口。 基本实现思路如下 设计环形缓冲区用于接收数据, 串口中断中将接收的数据写入接收缓冲区,如果满则丢弃,接收应用接口则查询该接收缓冲区如果缓冲区中数据不够则等待直到超时,收到多少就返回多少。 以上接收中断和应用接口都需要操作该缓冲区所以需要临界段处理。 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发送APIint 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接收APIuint32_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
可以调整查询间隔和缓冲区大小来适应不同的传输速度。
七.重定向标准输入输出
以上实现了串口驱动,我们现在来实现串口重定向标准输入输出以方便后面的调试,处于资源和效率考虑,我们这里移植小型的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 八.命令行实现以上实现了串口重定向,我们现在实现简单的命令行,以便后面进行交互操作。 设计思想是,定义命令字符串和实现函数的对应表,标准输入读一行,搜索对应表和字符串匹配,匹配则执行对应的函数。 实现代码见shell.c shell.h shell_func.c shell_func.h
测试 输入help回车打印如下
九. ADC采集音频使用如下麦克风采集模块 ADC采集信号。 参考电压为3.3V
配置ADC 配置P001为ADC的CH1 添加相关代码 配置属性
生成工程
添加adc.c和adc.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); }
也可以添加命令行采集
Shell_func.h中 void ADCFun(void* param);
Shell_func.c中 #include "adc.h" { (const uint8_t*)"adc", ADCFun, "adc"}, /*ADC采集*/ 十. ADC可视化(虚拟示波器)通过串口将采集的数据发送到PC,PC端使用可视化上位机进行可视化,即实现了虚拟示波器的应用。 十一.DSP算法库添加DSP算法库 使用的是CMSIS-DSP的算法库
添加的代码如下 十二.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.h见git 12.4测试上位机输入fft 10 即进行10次采样分析 效果如下 十三.噪声检测与分析我们也可以将开发板打造为噪声监测分析仪,对于噪声首先关心的就是其大小,我们可以实时采集声音并通过算法检测出极大值,最终换算成相对基准功率的噪声分贝值。同时也可以通过命令行控制何时进行采样分析,和其他功能是独立的,可以单独调用,集成在一起作为工具集供调用。当然也可以进行噪声的谐波等分析,和前面的点那个质量谐波分析等一样。 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次采样分析 可视化显示如下,黄色线是极大值检测结果,蓝色线是原始数据。
十四.数字滤波器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次采样分析 可视化显示如下,黄色线滤波结果,蓝色线是原始数据。 十五.相位频率幅值分析(频率计等)15.1相位分析FFT计算结果,幅值最大的横坐标对应信号频率,纵坐标对应幅度。幅值最大的为out[m]=val;则信号频率f0=(Fs/N)m ,信号幅值Vpp=val/(N/2)。N为FFT的点数,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次采样分析
十六. 总结本开发板性能资源比较丰富,特别适合开发和验证等。本文基于该开发板实现了信号处理前端,实现了虚拟示波器,噪声分析仪,电能质量分析仪,数字滤波器等功能的集合,并且可以方便的快速添加更多的应用。可以基于CLI调用方便脚本化使用,可以使用上位机可视化,是一个瑞士军刀类型的工具集。目前支持的CLI命令如下,可以快速扩展更多应用。
|