MAX32660-EVSYS介绍
MAX32660 微控制器
· Arm Cortex-M4内核, 工作频率96MHz · 256KB Flash Memory · 96KB SRAM · 16KB 指令缓存
MAX32660最小系统部分
· DIP双排通孔设计,孔间距2.54mm · 可支持面包板 · 一个用户LED · 一个用户按键
基于MAX32625PICO调试功能部分
· 支持CMSIS DAP SWD调试器 · 一路虚拟串口
MAX32660,超低功耗、高成效、高度集成微控制器
该评估板核心为 MAX32660 微控制器,属于美信 DARWIN 产品系列,是一款超低功耗、性价比突出、集成度非常高的32位控制器。芯片封装非常小,4mm x 4mm 的 TQFN 已经是这个系列里最大封装,非常适合电池供电或是无线传感器的应用。
MAX32660 采用了带浮点运算功能的 Cortex-M4 内核,最大主频96MHz,带256KB Flash和96KB SRAM,在如此小的体积下,性能还是比较强的。
应用场景:
· 便携式医疗设备 · 运动手表 · 可穿戴医疗设备
引脚功能:
硬件连接
LCD:
SCL -> P0_6 (SPI0A_SCK)
SDA -> P0_5 (SPI0A_MOSI)
RES -> P0_9 (GPIO)
SDA -> P0_8 (GPIO)
MAX30102:
SCL -> P0_2 (IIC1A_SCL)
SDA -> P0_3 (IIC1A_SDA)
UART:
TXD -> P0_10 (UART1A_TX)
RXD -> P0_11 (UART1A_RX)
实现功能
系统框架
使用了 rt-thread 实时操作系统,这里在默认 BSP 配置下开启 SPI0 及 I2C1。同时关闭了软件模拟 I2C 来解决一个 BSP 生成工程时报的缺少文件错误。
同时编写配置文件,增加写好的LCD驱动与MAX30102驱动
Import('RTT_ROOT')
Import('rtconfig')
from building import *
cwd = GetCurrentDir()
src = Split('''
drv_lcd.c
drv_max30102.c
''')
path = [cwd]
group = DefineGroup('Device', src, depend = [''], CPPPATH = path)
Return('group')
时钟显示
通过调用 rt_tick_get_millisecond() 函数,来获取当前系统已运行的时间。结合初始设定的时间,即可算出当前时间。
// 初始时间定义
#define HOUR 20
#define MINUTE 59
#define SECOND 59
void ClockProcess(void)
{
uint32_t sec = rt_tick_get_millisecond() / 1000;
sec += HOUR * 3600 + MINUTE * 60 + SECOND;
uint8_t s = sec % 60;
uint8_t m = (sec / 60) % 60;
uint8_t h = (sec / 60 / 60) % 24;
lcd_show_string(10, 100, 32, "%02d:%02d:%02d", h, m, s);
}
LCD显示驱动
LCD显示驱动移植自 RT-Thread 潘多拉 STM32L475 中 LCD 例程,使用的是SPI0,这里不过多介绍。
static int rt_hw_lcd_init(void)
{
rt_hw_spi_device_attach("spi0", "spi00", LCD_CS_PIN);
lcd_gpio_init();
/* Memory Data Access Control */
lcd_write_cmd(0x36);
lcd_write_data(0x00);
/* RGB 5-6-5-bit */
lcd_write_cmd(0x3A);
lcd_write_data(0x65);
/* Porch Setting */
lcd_write_cmd(0xB2)
...
}
心率测量
static struct rt_i2c_bus_device *i2c_dev;
static void write_reg(uint8_t addr, uint8_t data)
{
uint8_t buf[] = {addr, data};
struct rt_i2c_msg send_msg = {0};
send_msg.addr = MAX30102_ADDR;
send_msg.flags = RT_I2C_WR;
send_msg.buf = buf;
send_msg.len = sizeof(buf);
rt_i2c_transfer(i2c_dev, &send_msg, 1);
}
static void read_reg(uint8_t addr, uint8_t *data, uint16_t len)
{
struct rt_i2c_msg send_msg[2] = {0};
send_msg[0].addr = MAX30102_ADDR;
send_msg[0].flags = RT_I2C_WR;
send_msg[0].buf = &addr;
send_msg[0].len = 1;
send_msg[1].addr = MAX30102_ADDR;
send_msg[1].flags = RT_I2C_RD;
send_msg[1].buf = data;
send_msg[1].len = len;
rt_i2c_transfer(i2c_dev, send_msg, 2);
}
void max30102_init(void)
{
i2c_dev = (struct rt_i2c_bus_device *)rt_device_find("i2c1");
if (i2c_dev == RT_NULL) {
rt_kprintf("Err: Not find i2c1.");
return;
}
write_reg(REG_INTR_ENABLE_1, 0xc0);
write_reg(REG_INTR_ENABLE_2, 0x00);
write_reg(REG_FIFO_WR_PTR, 0x00);
write_reg(REG_OVF_COUNTER, 0x00);
write_reg(REG_FIFO_RD_PTR, 0x00);
write_reg(REG_FIFO_CONFIG, 0x4f);
write_reg(REG_MODE_CONFIG, 0x03);
write_reg(REG_SPO2_CONFIG, 0x27);
write_reg(REG_LED1_PA, 0x24);
write_reg(REG_LED2_PA, 0x24);
write_reg(REG_PILOT_PA, 0x7f);
}
void max30102_read_fifo(uint32_t *red_data, uint32_t *ir_data)
{
uint8_t temp = 0;
uint8_t buf[6] = {0};
read_reg(REG_INTR_STATUS_1, &temp, 1);
read_reg(REG_INTR_STATUS_2, &temp, 1);
read_reg(REG_FIFO_DATA, buf, sizeof(buf));
*red_data = ((uint32_t)buf[0] << 16) + ((uint32_t)buf[1] << 8) + buf[2];
*ir_data = ((uint32_t)buf[3] << 16) + ((uint32_t)buf[4] << 8) + buf[5];
}
通过调用 max30102_read_fifo() 获取红光测量值,并使用前项差分法对数据进行处理,即将新数据减去上次获取的数据。 通过输出成波形可以比较清楚地发现每次向下的尖峰即为一次心跳。(好文章: https://mp.weixin.qq.com/s/RhFktwAu6L0a6wkFoBRm0A)
因此这里设定了一个固定阈值 FINGER_PRESS_MIN,当红光数据小于该值认为手指按下;并当红光差分数据小于 -500 时,认为检测到一次心跳,且要再次检测 15 个数据之后,才能检测下一次心跳,来避免连续判断同一个尖峰区间为一次心跳,具体代码如下。
if (red_data > FINGER_PRESS_MIN) { // 手指按下
rt_uint16_t last_beat_num = beat_num;
static rt_uint32_t measure_time = 0;
if (diff_data < -500 && count > 15) { // 判断到一次心跳
count = 0;
if (beat_num == 0) {
lcd_clear(BLACK);
lcd_show_string(50, 210, 24, "Measuring...");
}
if (beat_num++ == 5) {
measure_time = rt_tick_get_millisecond();
lcd_fill(0, 210, 240, 240, BLACK);
lcd_show_string(10, 210, 24, "beat:");
lcd_show_string(135, 210, 24, "rate:");
}
if (beat_num < 5) {
continue;
}
// 计算心率
heart_rate = (rt_uint32_t)(beat_num - 5) * 1000 * 60 / (rt_tick_get_millisecond() - measure_time);
// 输出心跳数及心率到屏幕
if (last_beat_num != beat_num && beat_num > 5) {
lcd_show_string(80, 210, 24, "%-3d ", beat_num - 5);
lcd_set_color(BLACK, RED);
lcd_show_string(195, 210, 24, "%-3d", heart_rate);
lcd_set_color(BLACK, WHITE);
}
}
}
同时还将得到的差分数据在LCD上实时进行倒置输出,可以得到一个类似心电图的跳动趋势图:
代码实现如下:
static void draw_heart_beat(rt_int16_t data)
{
static rt_uint8_t time = 0;
time += 2;
if (time >= 239) {
lcd_fill(0, 0, 240, 205, BLACK);
}
time = time % 240;
data = (data + 2500) < 0 ? 0 : (data + 2500);
data /= 15;
data = data < 5 ? 5 : (data > 200 ? 200 : data);
lcd_draw_point(time, data);
}
心得体会
第一次用资源这么紧凑的MCU,引脚能复用成多种功能,各种主流接口基本都有,有些超乎到手前的想象。几年前草草地做过心率传感器的项目,但涉及心率检测这块用的别人的代码一直没怎么理解,恰逢推送了相关的文章,能重写当时不会的东西,确实感到挺高兴。