很荣幸获得了RA生态工作室举办的RA4M2-SENSOR开发板评测活动,板子已经收到,由于我用不惯官方的IDE,所以评测都是以Keil为主。
本次进行任务1中的Analogue和Timers两部分的综合测试,电容式触摸感应单元(CTSU)功能在e2stduio上可以正常运行,keil上无法正常运行,问题还没有找到,解决了后续会分享相关测试。
板载资源介绍如下:
1.两个 USB 全速 2.0 主机(Type-C 连接器)
2.两种烧录方式
SWD 烧录口:编程或调试 PC
串口烧录:打印数据或烧录
3.用户指示灯和按钮
三个用户指示灯(红色和黄色
两个串口指示灯(红色)指示调试连接
两个电源指示灯(红色)
三个用户按键, 一个复位按键
4.外部晶振
低速晶振: 32.768KHz
高速晶振: 12MHz
5.生态系统扩展
微控制器全部引脚由 CN3、 CN4、 CN7、 CN8 引出
传感器(SENSOR)系列 扩展连接器
这次虽然只是进行Analogue和Timers两部分的综合测试,但是板载的LED、按钮、Uart一个不漏也全部测试一遍。
项目创建过程就不再赘述了,网上的教程多如牛毛,数不胜数。下面我们直接进行所有资源配置的相关介绍。
板载的LED、按钮、Uart的原理图如下:



根据原理图上的引脚定义进行配置,
1.按键配置如下:



2.uart配置如下:


3.led配置如下:


D3通过PWM控制,所以配置有所不同,如下:

4.ADC(只启用通道0)软件触发、连续扫描模式,配置如下:

引脚原理图如下:




5.DAC0配置如下:

P014引脚原理图如下:


6.Timer0 普通定时1s,控制LED D6闪烁,配置如下:

7.Timer2 PWM输出控制LED D3,配置如下


P103外引引脚原理图如下:

8.Timer5 输入捕获(PWM脉宽和周期测量,测试Timer2产生的PWM波),配置如下




P101外引引脚原理图如下:

如上,这次需要测试的功能模块的所有配置已经完成了,点击如下红框内的按钮就生成Keil工程代码

下面进行功能验证代码的介绍,
1.systick功能
提供系统时基,代码如下:
volatile uint64_t SysTickms = 0;
fsp_err_t SystickInit(void)
{
uint32_t uwSysclk = R_BSP_SourceClockHzGet(FSP_PRIV_CLOCK_PLL);
if (SysTick_Config(uwSysclk / 1000) != 0)
{
return FSP_ERR_ASSERTION;
}
return FSP_SUCCESS;
}
void SysTick_Handler(void)
{
SysTickms += 1;
}
在主函数中调用SystickInit()即可。
2.uart
主要用于输出调试信息,
这里移植了一个letter_shell和lwrb库,letter_shell实现了类似linux控制台命令行的交互效果,源码地址如下:
https://github.com/NevermindZZT/letter-shell
lwrb库用来处理接收到的串口数据,直接丢入ringbuf中,需要的时候取出来数据使用即可,源码地址如下:
https://github.com/MaJerle/lwrb
具体移植方法可以阅读源码里的README.md文档。
工程中代码如下
#include "lwrb/lwrb.h"
#include "shell_port.h"
lwrb_t buff = {0};
uint8_t buff_data[256];
err = g_uart9.p_api->open(g_uart9.p_ctrl, g_uart9.p_cfg);
assert(FSP_SUCCESS == err);
lwrb_init(&buff, buff_data, sizeof(buff_data));
userShellInit();
串口中断函数如下
static volatile bool g_uart9_tx_complete = false;
static volatile bool g_uart9_rx_complete = false;
void user_uart9_callback (uart_callback_args_t * p_args)
{
switch (p_args->event)
{
case UART_EVENT_TX_DATA_EMPTY:
{
break;
}
case UART_EVENT_TX_COMPLETE:
{
g_uart9_tx_complete = true;
break;
}
case UART_EVENT_RX_COMPLETE:
{
g_uart9_rx_complete = true;
break;
}
case UART_EVENT_RX_CHAR:
{
lwrb_write(&buff, (uint8_t*)&p_args->data, 1);
break;
}
default:
{
break;
}
}
}
void uart9_wait_for_tx(void)
{
while (!g_uart9_tx_complete);
g_uart9_tx_complete = false;
}
void uart9_wait_for_rx(void)
{
while (!g_uart9_rx_complete);
g_uart9_rx_complete = false;
}
letter_shell其他接口移植函数如下:
#include "shell.h"
#include "hal_data.h"
#include "log.h"
Shell shell;
char shellBuffer[256];
#if (HELL_TASK_WHILE == 1)
#include "FreeRTOS.h"
#include "task.h"
static SemaphoreHandle_t shellMutex;
#endif
short userShellWrite(char *data, unsigned short len)
{
g_uart9.p_api->write(g_uart9.p_ctrl, (uint8_t const *const)data, (uint32_t)len);
uart9_wait_for_tx();
return (short)len;
}
short userShellRead(char *data, unsigned short len)
{
(void)data;
(void)len;
return 0;
}
int userShellLock(Shell *sh)
{
(void)(sh);
#if (HELL_TASK_WHILE == 1)
xSemaphoreTakeRecursive(shellMutex, portMAX_DELAY);
#endif
return 0;
}
int userShellUnlock(Shell *sh)
{
(void)(sh);
#if (HELL_TASK_WHILE == 1)
xSemaphoreGiveRecursive(shellMutex);
#endif
return 0;
}
void uartLogWrite(char *buffer, short len);
Log uartLog = {
.write = uartLogWrite,
.active = 1,
.level = LOG_DEBUG
};
void uartLogWrite(char *buffer, short len)
{
if (uartLog.shell)
{
shellWriteEndLine(uartLog.shell, buffer, len);
}
}
void userShellInit(void)
{
#if (HELL_TASK_WHILE == 1)
shellMutex = xSemaphoreCreateMutex();
shell.lock = userShellLock;
shell.unlock = userShellUnlock;
#endif
shell.write = userShellWrite;
shellInit(&shell, shellBuffer, sizeof(shellBuffer));
logRegister(&uartLog, &shell);
#if (HELL_TASK_WHILE == 1)
if (xTaskCreate(shellTask, "shell", 256, &shell, 5, NULL) != pdPASS)
{
logError("shell task creat failed");
}
#endif
}
static const char *shellText[] =
{
#if SHELL_SHOW_INFO == 1
[SHELL_TEXT_INFO] =
"\\\\r\\\\n"
#if 0
" _ _ _ _ _ _ \\\\r\\\\n"
"| | ___| |_| |_ ___ _ __ ___| |__ ___| | |\\\\r\\\\n"
"| | / _ \\\\\\\\ __| __/ _ \\\\\\\\ '__| / __| '_ \\\\\\\\ / _ \\\\\\\\ | |\\\\r\\\\n"
"| |__| __/ |_| || __/ | \\\\\\\\__ \\\\\\\\ | | | __/ | |\\\\r\\\\n"
"|_____\\\\\\\\___|\\\\\\\\__|\\\\\\\\__\\\\\\\\___|_| |___/_| |_|\\\\\\\\___|_|_|\\\\r\\\\n"
#else
" ____ ___ __ __ __ ______ _____ \\\\r\\\\n"
" / __ \\\\\\\\/ | / // / / |/ /__ \\\\\\\\ / ___/___ ____ _________ _____\\\\r\\\\n"
" / /_/ / /| |/ // /_/ /|_/ /__/ / \\\\\\\\__ \\\\\\\\/ _ \\\\\\\\/ __ \\\\\\\\/ ___/ __ \\\\\\\\/ ___/\\\\r\\\\n"
" / _, _/ ___ /__ __/ / / // __/ ___/ / __/ / / (__ ) /_/ / / \\\\r\\\\n"
"/_/ |_/_/ |_| /_/ /_/ /_//____/____/____/\\\\\\\\___/_/ /_/____/\\\\\\\\____/_/ \\\\r\\\\n"
" /_____/ \\\\r\\\\n"
#endif
"\\\\r\\\\n"
"Build: "__DATE__" "__TIME__"\\\\r\\\\n"
"Version: "SHELL_VERSION"\\\\r\\\\n"
"Copyright: (c) 2020 Letter\\\\r\\\\n",
#endif
[SHELL_TEXT_CMD_TOO_LONG] =
"\\\\r\\\\nWarning: Command is too long\\\\r\\\\n",
[SHELL_TEXT_CMD_LIST] =
"\\\\r\\\\nCommand List:\\\\r\\\\n",
[SHELL_TEXT_VAR_LIST] =
"\\\\r\\\\nVar List:\\\\r\\\\n",
[SHELL_TEXT_USER_LIST] =
"\\\\r\\\\nUser List:\\\\r\\\\n",
[SHELL_TEXT_KEY_LIST] =
"\\\\r\\\\nKey List:\\\\r\\\\n",
[SHELL_TEXT_CMD_NOT_FOUND] =
"Command not Found\\\\r\\\\n",
[SHELL_TEXT_POINT_CANNOT_MODIFY] =
"can't set pointer\\\\r\\\\n",
[SHELL_TEXT_VAR_READ_ONLY_CANNOT_MODIFY] =
"can't set read only var\\\\r\\\\n",
[SHELL_TEXT_NOT_VAR] =
" is not a var\\\\r\\\\n",
[SHELL_TEXT_VAR_NOT_FOUND] =
"Var not Fount\\\\r\\\\n",
[SHELL_TEXT_HELP_HEADER] =
"command help of ",
[SHELL_TEXT_PASSWORD_HINT] =
"Please input password:",
[SHELL_TEXT_PASSWORD_ERROR] =
"\\\\r\\\\npassword error\\\\r\\\\n",
[SHELL_TEXT_CLEAR_CONSOLE] =
"\\\\033[2J\\\\033[1H",
[SHELL_TEXT_CLEAR_LINE] =
"\\\\033[2K\\\\r",
[SHELL_TEXT_TYPE_CMD] =
"CMD ",
[SHELL_TEXT_TYPE_VAR] =
"VAR ",
[SHELL_TEXT_TYPE_USER] =
"USER",
[SHELL_TEXT_TYPE_KEY] =
"KEY ",
[SHELL_TEXT_TYPE_NONE] =
"NONE",
#if SHELL_EXEC_UNDEF_FUNC == 1
[SHELL_TEXT_PARAM_ERROR] =
"Parameter error\\\\r\\\\n",
#endif
};
在while循环中调用如下函数就完成了letter_shell的功能,同时也完成了自带的log输出功能。
if (lwrb_get_full(&buff))
{
char ch = '0';
lwrb_read(&buff,&ch,1);
shellHandler(&shell, ch);
}
效果如下:

3.按键
移植了MultiButton库,实现按键的单击、双击、长按的处理,MultiButton源码地址如下:
https://github.com/0x1abin/MultiButton
具体移植方法可以阅读源码里的README.md文档。工程中代码如下
#include "multi_button.h"
enum Button_IDs {
btn1_id = 0,
btn2_id,
btn3_id,
btn_max,
};
struct Button btn[btn_max];
uint8_t read_button_GPIO(uint8_t button_id)
{
bsp_io_level_t btnState = BSP_IO_LEVEL_HIGH;
switch(button_id)
{
case btn1_id:
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_00_PIN_13, &btnState);
break;
case btn2_id:
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_12, &btnState);
break;
case btn3_id:
R_IOPORT_PinRead(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_11, &btnState);
break;
default:
break;
}
return (uint8_t)btnState;
}
void Callback_btn_Handler(void *btn_ptr)
{
Button *_btn = (Button *)btn_ptr;
switch (_btn->event)
{
case PRESS_DOWN:
logInfo("BTN[%d] press down!",_btn->button_id+1);
break;
case PRESS_UP:
logInfo("BTN[%d] press up!",_btn->button_id+1);
break;
case PRESS_REPEAT:
logInfo("BTN[%d] press repeat!",_btn->button_id+1);
break;
case SINGLE_CLICK:
logInfo("BTN[%d] single click!",_btn->button_id+1);
break;
case DOUBLE_CLICK:
logInfo("BTN[%d] double click!",_btn->button_id+1);
break;
case LONG_PRESS_START:
logInfo("BTN[%d] long press start!",_btn->button_id+1);
break;
case LONG_PRESS_HOLD:
logInfo("BTN[%d] long press hold!",_btn->button_id+1);
break;
default:
break;
}
}
for (uint8_t i = 0; i < btn_max; i++) {
button_init(&btn[i], read_button_GPIO, 0, i);
button_attach(&btn[i], PRESS_DOWN, Callback_btn_Handler);
button_attach(&btn[i], PRESS_UP, Callback_btn_Handler);
button_attach(&btn[i], PRESS_REPEAT, Callback_btn_Handler);
button_attach(&btn[i], SINGLE_CLICK, Callback_btn_Handler);
button_attach(&btn[i], DOUBLE_CLICK, Callback_btn_Handler);
button_attach(&btn[i], LONG_PRESS_START, Callback_btn_Handler);
button_attach(&btn[i], LONG_PRESS_HOLD, Callback_btn_Handler);
button_start(&btn[i]);
}
// 按键任务处理函数,需要在while循环中5ms周期调用
if (btn_tick < SysTickms)
{
button_ticks();
btn_tick = SysTickms + 5;
}
到此就完成了按键的功能测试,按下按键后终端控制台会打印相应的信息,效果如下:

3.ADC和TSN
uint16_t adc_val[8];
float temperature = 0.0f;
int gi_temperature = 0;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_INT), gettemperature, &gi_temperature, test); // 用于通过shell获取温度,放大100倍显示
logDebug("Start Open adc0");
err = g_adc0.p_api->open(g_adc0.p_ctrl,g_adc0.p_cfg);
assert(FSP_SUCCESS == err);
err = g_adc0.p_api->scanCfg(g_adc0.p_ctrl,g_adc0.p_channel_cfg);
assert(FSP_SUCCESS == err);
logDebug("Open adc0 end");
logDebug("Start adc0 scan");
g_adc0.p_api->scanStart(g_adc0.p_ctrl);
如上代码在入口函数中执行一次即可。
adc中断函数如下:
volatile bool scan_complete_flag = false;
void user_adc_callback(adc_callback_args_t * p_args)
{
FSP_PARAMETER_NOT_USED(p_args);
scan_complete_flag = true;
}
void adc_wait_for_scan(void)
{
while (!scan_complete_flag);
scan_complete_flag = false;
}
bool get_adc_scan_status(void)
{
return scan_complete_flag;
}
void reset_adc_scan_state(void)
{
scan_complete_flag = false;
}
4.ADC数据读取及TSN计算代码如下,
if (get_adc_scan_status())
{
g_adc0.p_api->read(g_adc0.p_ctrl,ADC_CHANNEL_0,&adc_val[0]);
g_adc0.p_api->read(g_adc0.p_ctrl,ADC_CHANNEL_TEMPERATURE,&adc_val[1]);
int32_t cal127;
adc_info_t adc_info;
(void) R_ADC_InfoGet(&g_adc0_ctrl, &adc_info);
cal127 = (int32_t) adc_info.calibration_data;
float slope = 4.0f/1000.0f;
float v1= 3.3f * (float)cal127 / 4096.0f;
float vs = 3.3f * (float)adc_val[1] /4096.0f;
temperature = (vs - v1) / slope + 127.0f;
gi_temperature = (int)(temperature * 100.0f);
reset_adc_scan_state();
}
ADC的通道上外接了了一个光敏电阻传感器模块,可以通过读取其数值来控制led D2的亮灭,代码如下:
if (led1_tick < SysTickms)
{
led1_tick = SysTickms + 100;
g_ioport.p_api->pinWrite(g_ioport.p_ctrl, BSP_IO_PORT_00_PIN_02, (adc_val[0]>2000));
}
如上代码需要在while循环中周期执行,至此,ADC和TSN的代码介绍完毕,效果如下
通过shell获取温度

光敏电阻控制led,接近光影D2熄灭,拿开D2点亮。

5.DAC0,输出正弦波代码如下:
//正弦波数据数组变量,根据计算得来
const uint16_t sindata[] = {
2048, 2460, 2856, 3218, 3532, 3786, 3969, 4072, 4093, 4031, 3887, 3668,
3382, 3042, 2661, 2255, 1841, 1435, 1054, 714, 428, 209, 65, 3, 24, 127,
310, 564, 878, 1240, 1636, 2048
};
logDebug("Start Open and start dac0");
err = g_dac0.p_api->open(g_dac0.p_ctrl,g_dac0.p_cfg);
assert(FSP_SUCCESS == err);
err = g_dac0.p_api->start(g_dac0.p_ctrl);
assert(FSP_SUCCESS == err);
logDebug("Open dac0 end");
// 1ms dac-->sin while循环中周期调用
if (_ms != SysTickms)
{
if (sindataindex >= 32) {
sindataindex = 0;
}
g_dac0.p_api->write(g_dac0.p_ctrl,sindata[sindataindex++]);
// DAC_SetVoltage(vol);
}
通过示波器测量P014引脚,效果如头部视频所示。
6.Timer0,1s周期控制led D6闪烁,代码如下:
logDebug("Start Open and start timer0");
err = g_timer0.p_api->open(g_timer0.p_ctrl,g_timer0.p_cfg);
assert(FSP_SUCCESS == err);
err = g_timer0.p_api->start(g_timer0.p_ctrl);
assert(FSP_SUCCESS == err);
logDebug("Open timer0 end");
void user_timer0_callback(timer_callback_args_t * p_args)
{
static bsp_io_level_t level_led3 = BSP_IO_LEVEL_HIGH;
if (TIMER_EVENT_CYCLE_END == p_args->event)
{
g_ioport.p_api->pinWrite(g_ioport.p_ctrl, BSP_IO_PORT_01_PIN_04, level_led3);
level_led3 = !level_led3;
}
}
将光敏电阻靠近光源来控制D2熄灭,突出D6的闪烁效果,如下:

7.Timer2 输出PWM波形,代码如下:
uint8_t gpt2_pwma_duty = 20
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_CHAR), varPwm, &gpt2_pwma_duty, test)
void GPT2_PWMA_SetDuty(uint8_t duty)
{
timer_info_t info;
uint32_t current_period_counts;
uint32_t duty_cycle_counts;
if (duty > 100)
duty = 100;
g_timer0.p_api->infoGet(g_timer2.p_ctrl, &info);
current_period_counts = info.period_counts;
duty_cycle_counts = (uint32_t)(((uint64_t) current_period_counts * duty) / 100);
g_timer0.p_api->dutyCycleSet(g_timer2.p_ctrl, duty_cycle_counts, GPT_IO_PIN_GTIOCA);
}
logDebug("Start Open and start timer2 pwm");
err = g_timer2.p_api->open(g_timer2.p_ctrl,g_timer2.p_cfg);
assert(FSP_SUCCESS == err);
err = g_timer2.p_api->start(g_timer2.p_ctrl);
assert(FSP_SUCCESS == err);
GPT2_PWMA_SetDuty(gpt2_pwma_duty);
logDebug("Open timer2 pwm end");
// while循环里周期调用,修改gpt2_pwma_duty的数值即可改变占空比,默认20%
GPT2_PWMA_SetDuty(gpt2_pwma_duty);
通过逻辑分析仪测量P103或者观察LED D3的光亮强度可以反馈占空比的大小。
30%

50%

80%

通过修改占空比,肉眼可见LED D3光亮强度会改变

8.Timer5 输入捕获,测量PWM周期、频率、占空比,代码如下:
timer_info_t info;
uint32_t period;
uint32_t pwm_period;
uint32_t pwm_high_level_time;
uint32_t pwm_freq;
uint32_t pwm_duty;
logDebug("Start Open and start timer5 cap");
err = g_timer5.p_api->open(g_timer5.p_ctrl,g_timer5.p_cfg);
assert(FSP_SUCCESS == err);
err = g_timer5.p_api->infoGet(g_timer5.p_ctrl,&info);
assert(FSP_SUCCESS == err);
period = info.period_counts;
err = g_timer5.p_api->enable(g_timer5.p_ctrl);
assert(FSP_SUCCESS == err);
err = g_timer5.p_api->start(g_timer5.p_ctrl);
assert(FSP_SUCCESS == err);
logDebug("Open timer5 cap end");
// while循环中1s周期执行,打印结果
if (_sec < SysTickms)
{
_sec = SysTickms + 1000;
/* 计算PWM的频率 */
pwm_freq = info.clock_frequency / pwm_period;
/* 计算PWM的占空比 */
pwm_duty = pwm_high_level_time * 100 / pwm_period;
// 打印
logInfo("High=%d, Period=%d, ", pwm_high_level_time, pwm_period);
logInfo("Friquency = %dHz, Duty_Cycle = %d%%", pwm_freq, pwm_duty);
pwm_period = pwm_high_level_time = pwm_freq = 0; //打印完后旧数据清零
}
void user_timer5_callback(timer_callback_args_t * p_args)
{
static uint32_t a_time;
static uint32_t b_time;
static uint32_t c_time;
static uint32_t overflow_times;
static uint8_t one_period_flag=0;
switch(p_args->event)
{
case TIMER_EVENT_CAPTURE_A:
if (0 == one_period_flag)
{
a_time = p_args->capture;
overflow_times = 0;
one_period_flag ++;
}
else if (1 == one_period_flag)
{
c_time = p_args->capture + overflow_times * period;
pwm_period = c_time - a_time;
overflow_times = 0;
one_period_flag = 0;
}
break;
case TIMER_EVENT_CAPTURE_B:
if (1 == one_period_flag)
{
b_time = p_args->capture + overflow_times * period;
pwm_high_level_time = b_time - a_time;
}
break;
case TIMER_EVENT_CYCLE_END:
overflow_times++;
break;
default:
break;
}
}
直接测量Timer2 P103引脚的信号,将P103和P101连接在一起,效果如下:
默认20%,20KHz.

视频效果见底部视频
keil整体工程:*附件:RA4M2_Sensor.7z