FastBond智能可穿戴之智能手表原型
MAX32660主控芯片,ADXL345测量倾角,SHT40测量温湿度,配有SS1306 12864OLED屏,通过RTC计算、显示万年历、倾角、温湿度信息,时间每秒刷新一次,倾角、温湿度每一分钟刷新一次,按键可即刻更新倾角、温湿度信息。
标签
嵌入式系统
MAX32660
ADXL345
SHT40
葉SiR
更新2021-11-29
1312

项目介绍

本项目以美信的MAX32660为主控芯片,配有两种传感器,均通过I2C与MCU通信,并将传感器信息及MCU内部RTC计算的万年历信息显示在OLED屏上,可以作为智能手表的原型。

使用器件

MAX32660-EVSYS

MAX32660详细介绍可参见:

👉 Funpack 第六期

MAX32660,低功耗Arm Cortex-M4 FPU处理器,带基于FPU的微控制器(MCU),256KB Flash和96KB SRAM。其主板上已安装基于MAX32625PICO的调试适配器;完成编程后,可将其直接拆卸。其外设资源包含:

1. 最大14路 GPIO
2. 最大两路 SPI
3. 一路 I2S
4. 最大两路 UART
5. 最大两路 I2C
6. 四通道标准DMA控制器
7. 3路32位计时器
8. 看门狗计时器
9. RTC 32.768kHz

MAX32660引脚图

ADXL345数字加速度计

是一款小而薄的超低功耗3轴加速度计,分辨率高(13位),测量范围达±16g。数字输出数据为16位二进制补码格式,可通过SPI(3线或4线)或I2C数字接口访问。可以在倾斜检测应用中测量静态重力加速度,还可以测量运动或冲击导致的动态加速度。其高分辨率(3.9mg/LSB),能够测量不到1.0°的倾斜角度变化。

该器件提供多种特殊检测功能:

  1. 活动和非活动检测功能通过比较任意轴上的加速度与用户设置的阈值来检测有无运动发生;
  2. 敲击检测功能可以检测任意方向的单振和双振动作;
  3. 自由落体检测功能可以检测器件是否正在掉落等。

ADXL345引脚配置和功能描述详细功能描述可参见网站及说明文档:

👉 得捷电子

Temp&Hum 15 Click

Mikroe的即插即用的传感器,内部含有SHT40以测量温湿度信息,I2C通信,原用于LPCS55S69-EVK上的,在此满足智能手表基本的传感器功能。

Temp & Hum 15 Click
详细功能描述可参见网站及说明文档:

👉 得捷电子

SS1306驱动的4线SPI 12864 OLED屏

经典OLED屏,虽然商家说SPI/I2C均可,但是得改电阻,在此使用4线SPI通信模式,原本想上u8g2的,但是移植不是很顺利。

系统引脚连接

   1. Temp & Hum 15 Click(SHT40):

外设引脚 芯片引脚
SCL P0_8
SDA P0_9

   2. ADXL345:

外设引脚 芯片引脚
SCL P0_8
SDA P0_9
SDO GND   
CS VCC
INT1 NC
INT2 NC

   3. OLED:

外设引脚 芯片引脚
CS P0_7
DC P0_3
RES P0_2
D1 P0_5
D0 P0_6

关键性代码及说明

SHT40获取温湿度信息

Click插件上所使用的SHT40详细说明可参见:

👉 SHT40说明文档

商家已提供了对应SHTx的驱动代码,参见:

👉 GitHub

在工程中,只需要按照对应的MCU,更改I2C读写部分的代码即可。在此使用MAX32660,根据帮助文档,对`sensirion_hw_i2c_implementation.c`内关于I2C初始化、读写的函数进行定制化的修改即可(头文件等省略):

// SHT40 and ADXL345 interrupt handler
void I2C0_IRQHandler(void)
{
    I2C_Handler(MXC_I2C0);
    return;
}

int16_t sensirion_i2c_select_bus(uint8_t bus_idx)
 {
    // IMPLEMENT or leave empty if all sensors are located on one single bus
    return STATUS_FAIL;
}

void sensirion_i2c_init(void) 
{
    //Setup the I2C0
	int error = 0;
	const sys_cfg_i2c_t sys_i2c_cfg = NULL;
	
    //I2C_Shutdown(SHT40_I2C);
    if((error = I2C_Init(SHT40_I2C, I2C_FAST_MODE, &sys_i2c_cfg)) != E_NO_ERROR) 
	{
        printf("Error initializing I2C0.(Error code = %d)\n", error);
		while(1);
    }
    NVIC_EnableIRQ(I2C0_IRQn);
}

void sensirion_i2c_release(void) 
{
    // IMPLEMENT or leave empty if no resources need to be freed
}

int8_t sensirion_i2c_read(uint8_t address, uint8_t* data, uint16_t count) 
{
	int error = 0;
	if((error = I2C_MasterRead(SHT40_I2C, (address << 1)|1, data, count, 0)) != count) 
	{
        printf("Error reading%d\n", error);
        return error;
    }
	return 0;
}

int8_t sensirion_i2c_write(uint8_t address, const uint8_t* data, uint16_t count) 
{
	int error = 0;
	if((error = I2C_MasterWrite(SHT40_I2C, (address << 1)|0, data, count, 0)) != count)
	{
        printf("Error writing %d\n", error);
        return error;
    }
	return 0;
}

void sensirion_sleep_usec(uint32_t useconds) 
{
	mxc_delay(useconds);
}

在主程序中,通过如下语句即可得出温湿度测量值(测量值已经过处理):

int32_t temperature, humidity;
int8_t ret = 0;
ret = sht4x_measure_blocking_read(&temperature, &humidity);
if (ret == STATUS_OK) 
{
	printf("measured temperature: %0.2f degreeCelsius, "
			"measured humidity: %0.2f percentRH\n", temperature / 1000.0f, humidity / 1000.0f);
} 
else 
{
	printf("error reading measurement\n");
}

ADXL获取倾角信息

关于ADXL345的资料网上挺多的,根据STM32等MCU的使用历程修改即可。修改I2C初始化、读写等即可:

#define ADXL345_I2C MXC_I2C0
int ADXL345_Init(void)
{    
	uint8_t error;
	
	//I2C_Shutdown(ADXL345_I2C);
	if((error = I2C_Init(ADXL345_I2C, I2C_FAST_MODE, NULL)) != E_NO_ERROR) 
	{
        printf("Error initializing I2C0.(Error code = %d)\n", error);
		while(1);
    }
    NVIC_EnableIRQ(I2C0_IRQn);	
      
    if(ADXL345_RD_Reg(DEVICE_ID) == 0xE5) //读取器件ID
    {  
		ADXL345_WR_Reg(INT_ENABLE, 0x00);
        ADXL345_WR_Reg(DATA_FORMAT, 0x0B);   //低电平中断输出,13位全分辨率,输出数据右对齐,16g量程 
        ADXL345_WR_Reg(BW_RATE, 0x0C);       //数据输出速度为400Hz
        ADXL345_WR_Reg(POWER_CTL, 0x38);     //链接使能,自动睡眠,测量模式    
        ADXL345_WR_Reg(OFSX, 0x00);
        ADXL345_WR_Reg(OFSY, 0x00);
        ADXL345_WR_Reg(OFSZ, 0x00); 
        return E_NO_ERROR;
    }           
    return 1;                                    
}

uint8_t ADXL345_Write_Len(uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf)
{
	uint8_t txdata[16], error;
	uint8_t i;
	txdata[0] = reg;
	for(i = 1; i < len+1; i++){
		txdata[i] = buf[i-1];
	}
	if((error = I2C_MasterWrite(ADXL345_I2C, (addr << 1)|0, txdata,  len+1, 0)) != len+1)
	{
        printf("Error writing %d in ADXL345_Write_Len!\n", error);
        return error;
    }
	return 0;
}

uint8_t ADXL345_Read_Len(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)
{
	uint8_t res;
	uint8_t error = 0;
	if((error = I2C_MasterWrite(ADXL345_I2C, (addr << 1)|0, &reg, 1, 0)) != 1)
	{
        printf("Error writing %d in ADXL345_Read_Len!\n", error);
        return error;
    }
	if((error = I2C_MasterRead(ADXL345_I2C, (addr << 1)|1, buf, len, 0)) != len) 
	{
        printf("Error reading %d in ADXL345_Read_Len!\n", error);
        return error;
    }
	return 0;
}

uint8_t ADXL345_WR_Reg(uint8_t reg,uint8_t data)
{
	uint8_t error;
	uint8_t txdata[2];
	txdata[0] = reg;
	txdata[1] = data;
	
	if((error = I2C_MasterWrite(ADXL345_I2C, ADXL345_WRITE, txdata, 2, 0)) != 2) 
	{
        printf("Error writing %d in ADXL345_WR_Reg!\n", error);
        return error;
    }
	return 0;
}

uint8_t ADXL345_RD_Reg(uint8_t reg)
{
	uint8_t error;
	uint8_t data;
	
	if((error = I2C_MasterWrite(ADXL345_I2C, ADXL345_WRITE, &reg, 1, 0)) != 1) 
	{
        printf("Error writing %d in ADXL345_RD_Reg!\n", error);
        return error;
    }
	if((error = I2C_MasterRead(ADXL345_I2C, ADXL345_READ, &data, 1, 0)) != 1) 
	{
        printf("Error reading %d in ADXL345_RD_Reg!\n", error);
        return error;
    }
	return data;
}

需要注意的是,初始化的设计需根据ADXL345手册对相应功能进行修改。接着,写测量倾角的功能函数:

void ADXL345_PROC(float *angle_x, float *angle_y, float *angle_z)
{
    short x, y, z;
	float x_acc_ad, y_acc_ad, z_acc_ad;
    float x_angle, y_angle, z_angle;   
    ADXL345_Read_Average(&x, &y, &z, 10);  //读取x,y,z 3个方向的加速度值 总共10次
	
	printf("Acc of X-axis: %.1f m/s2\n", x*1.0/256*9.8);
	printf("Acc of Y-axis: %.1f m/s2\n", y*1.0/256*9.8);
	printf("Acc of Z-axis: %.1f m/s2\n", z*1.0/256*9.8);
	
	x_acc_ad = x*1.0/32;
	y_acc_ad = y*1.0/32;
	z_acc_ad = z*1.0/32;
	
    x_angle = ADXL345_Get_Angle(x_acc_ad, y_acc_ad, z_acc_ad, 1);
    y_angle = ADXL345_Get_Angle(x_acc_ad, y_acc_ad, z_acc_ad, 2);
    z_angle = ADXL345_Get_Angle(x_acc_ad, y_acc_ad, z_acc_ad, 0);
	
	printf("Angle of X-axis: %.1f degree\n", x_angle);
	printf("Angle of Y-axis: %.1f degree\n", y_angle);
	printf("Angle of Z-axis: %.1f degree\n", z_angle);
    *angle_x = x_angle;
	*angle_y = y_angle;
	*angle_z = z_angle;
}

 

首先计算X、Y、Z轴加速度,再计算倾角,将两者信息均打印至串口。感觉历程给出的算法得出的数值不对,上述额外的校准计算部分参考如下:

👉 B站:ADXL345加速度计教程[HowToMechatronics]

再次需要注意的是,主程序中,初始化后需要自动校准(Auto Adjust)一次,此时需要持平ADXL345芯片以作为基准,如:

uint8_t xval = 0, yval = 0, zval= 0;
while(ADXL345_Init() != E_NO_ERROR)
{
	printf("ADXL345 initialization failed\n");
       mxc_delay(MXC_DELAY_SEC(1));
}
ADXL345_AUTO_Adjust(&xval, &yval, &zval);

通过加入下代码即可获取倾角值:

float angle_x, angle_y, angle_z;
ADXL345_PROC(&angle_x, &angle_y, &angle_z);

 

RTC万年历

使用MCU内部的RTC以实现两种功能:

  1. 万年历,初始化当前时间(年月日星期时分秒),将时间显示在OLED屏上并每秒刷新时间;
  2. 自动设置每分钟更新一次倾角、温湿度信息,将这些信息显示在OLED屏上。

RTC初始化及中断服务函数代码参考MAXIM提供的RTC例程,计算当前时间的函数(GetNowTime(),有修改)参考见:

👉 Funpack第六期--使用美信半导体MAX32660-EVSYS开发板制作的具有通知提醒和体温测量功能的手表原型-by叶开

在RTC初始化函数中,记录开始时的秒数;开启报警,每60秒触发报警中断,调用RTC中断服务函数。

/* in .h define the struct time_t*/
typedef struct 
{
    uint16_t year;
    uint8_t month;
    uint16_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
    uint8_t weekday;
    bool leap;
} time_t, *time_t_ptr;

/* code below in .c file */
#define TIME_OF_DAY_SEC 60
time_t nowTime;
uint32_t start_sec = 0;
sys_cfg_rtc_t sys_cfg = 
{
	.tmr = MXC_TMR0
};

static const uint32_t SECS_PER_MIN = 60;
static const uint32_t SECS_PER_HR = 60 * SECS_PER_MIN;
static const uint32_t SECS_PER_DAY = 24 * SECS_PER_HR;
/* RTC Initialization */
int Clock_Init(void) 
{	
	NVIC_EnableIRQ(RTC_IRQn);
	
	/* Set the time: 2021-11-27 23:56:00, not a leap year */
    nowTime.year = 2021;
    nowTime.month = 11;
    nowTime.day = 27;
    nowTime.hour = 23;
    nowTime.minute = 56;
    nowTime.second = 0;
    nowTime.leap = false;
	start_sec = nowTime.hour * SECS_PER_HR + nowTime.minute * SECS_PER_MIN + nowTime.second;
	
	if(RTC_Init(MXC_RTC, 0, 0, &sys_cfg) != E_NO_ERROR) 
	{
        printf("Failed RTC_Setup().\n");
        return -1;
    }
	
	if(RTC_SetTimeofdayAlarm(MXC_RTC, TIME_OF_DAY_SEC) != E_NO_ERROR) 
	{
        printf("Failed RTC_SetTimeofdayAlarm().\n");
        return -1;
    }
	
	if(RTC_EnableTimeofdayInterrupt(MXC_RTC) != E_NO_ERROR) 
	{
        printf("Failed RTC_EnableTimeofdayInterrupt().\n");
        return -1;
    }
	
    if(RTC_EnableRTCE(MXC_RTC) != E_NO_ERROR) 
	{
        printf("Failed RTC_EnableRTCE().\n");
        return -1;
    }
	
	return E_NO_ERROR;
}

 

RTC中断服务函数如下:

void RTC_IRQHandler(void)
{
    int time;
    int flags = RTC_GetFlags();
	
	/* Check time-of-day alarm flag. */
    if(flags & MXC_F_RTC_CTRL_ALDF) 
	{
        RTC_ClearFlags(MXC_F_RTC_CTRL_ALDF);
		
		// printTime();
		DataUpdate();
		
        /* Set a new alarm 10 seconds from current time. */
        time = RTC_GetSecond();
        if(RTC_SetTimeofdayAlarm(MXC_RTC, time + TIME_OF_DAY_SEC) != E_NO_ERROR) 
		{
            /* Handle Error */
        }
    }
}

中断发生,调用DataUpdate(),在该函数内进行温湿度、倾角信息的读取更新;并设置下一次60秒的报警。

参考的GetNowTime()函数存在bug,原本每次产生日翻转后会将设置的RTC初值减去24*60*60,但这样会改变期望实现的每一分钟自动报警的计数值,从而无法实现这一功能。另外,原函数的年翻转存在bug(即设置为2021-12-31 23:59:59,原因是未考虑12月31日进位一天)。

要想不影响自动报警功能,则不能在每次获取当前时间时改变RTC值,因此引入两个全局变量作为储存,每次获取时间做数值上的修正即可:

uint32_t new_start_sec = 0;
uint32_t start_sec = 0;
uint8_t dayRedress_leap[12] = 
{
	0, 3, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6
};

uint8_t dayRedress_common[12] = 
{
	0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5
};
void GetNowTime(time_t *nowTime)
{
    uint32_t day = 0, hr = 0, min = 0, sec = 0;
	
	sec = RTC_GetSecond() + start_sec;
	if(new_start_sec > 0)
		sec -= 24 * 60 * 60;

    day = sec / SECS_PER_DAY;
    sec -= day * SECS_PER_DAY;

    hr = sec / SECS_PER_HR;
    sec -= hr * SECS_PER_HR;

    min = sec / SECS_PER_MIN;
    sec -= min * SECS_PER_MIN;
		
	nowTime -> hour = hr;
    nowTime -> minute = min;
    nowTime -> second = sec;

    if (day >= 1) 
	{
        nowTime -> day++;
		new_start_sec = sec - 24 * 60 * 60;
    }
	
    if ((nowTime -> year % 400 == 0) || ((nowTime -> year % 100 != 0) && (nowTime -> year % 4 == 0))) 
	{
        nowTime -> leap = true;
    } 
	else 
	{
        nowTime -> leap = false;
    }

    switch (nowTime -> month) 
	{
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
			if (nowTime -> day >= 32) 
			{
                nowTime -> month++;
                nowTime -> day -= 31;
            }
            break;
        case 12:	// 年翻转bug修正
			if (nowTime -> day >= 32) 
			{
                nowTime -> month = 1;
				nowTime -> year++;
                nowTime -> day -= 31;
            }
            break;
        case 4:
        case 6:
        case 9:
        case 11:
            if (nowTime -> day >= 31) 
			{
                nowTime -> month++;
                nowTime -> day -= 30;
            }
            break;
        case 2:
            if (nowTime -> leap) 
			{
                if (nowTime -> day >= 30) 
				{
                    nowTime -> month++;
                    nowTime -> day -= 29;
                }
            } 
			else 
			{
                if (nowTime -> day >= 29) 
				{
                    nowTime -> month++;
                    nowTime -> day -= 28;
                }
            }
            break;
        default:
            break;
    }
	
    nowTime -> weekday = (nowTime -> year + nowTime -> year / 4 + nowTime -> year / 400 - nowTime -> year / 100
							+ (nowTime -> leap ? dayRedress_leap[nowTime -> month - 1] :
							dayRedress_common[nowTime -> month - 1]) + nowTime -> day - 1) % 7;
}

SS1306 12864 4-SPI OLED显示

对于OLED的4线SPI GPIO口初始化,需要先配置SCK/MOSI/RST/DC/CS IO口,再调用SPI初始化

/* in .h define the GPIO ports and pins */
#define SPI_SCK_PORT	PORT_0
#define SPI_SCK_PIN		PIN_6
#define SPI_MOSI_PORT	PORT_0
#define SPI_MOSI_PIN	PIN_5
#define SPI_CS_PORT 	PORT_0
#define SPI_CS_PIN 		PIN_7
#define SPI_RST_PORT 	PORT_0
#define SPI_RST_PIN 	PIN_2
#define SPI_DC_PORT 	PORT_0
#define SPI_DC_PIN 		PIN_3

/* code below in .c file */
void gpio_init(void)
{
	gpio_cfg_t gpio_SCK = 
	{
		.port = SPI_SCK_PORT,
		.mask = SPI_SCK_PIN,
		.pad = GPIO_PAD_NONE,
		.func = GPIO_FUNC_OUT,
	};
	GPIO_Config(&gpio_SCK);
	
	gpio_cfg_t gpio_RST = 
	{
		.port = SPI_RST_PORT,
		.mask = SPI_RST_PIN,
		.pad = GPIO_PAD_PULL_UP,
		.func = GPIO_FUNC_OUT,
	};
	GPIO_Config(&gpio_RST);
	
	gpio_cfg_t gpio_DC = 
	{
		.port = SPI_DC_PORT,
		.mask = SPI_DC_PIN,
		.pad = GPIO_PAD_NONE,
		.func = GPIO_FUNC_OUT,
	};
	GPIO_Config(&gpio_DC);
	
	gpio_cfg_t gpio_CS =
	{
		.port = SPI_CS_PORT,
		.mask = SPI_CS_PIN,
		.pad = GPIO_PAD_NONE,
		.func = GPIO_FUNC_OUT,
	};
	GPIO_Config(&gpio_CS);
	
	gpio_cfg_t gpio_MOSI =
	{
		.port = SPI_MOSI_PORT,
		.mask = SPI_MOSI_PIN,
		.pad = GPIO_PAD_NONE,
		.func = GPIO_FUNC_OUT,
	};
	GPIO_Config(&gpio_MOSI);
	
	GPIO_OutSet(&gpio_SCK);
	GPIO_OutSet(&gpio_MOSI);
	GPIO_OutSet(&gpio_RST);
	GPIO_OutSet(&gpio_DC);
	GPIO_OutSet(&gpio_CS);
}
void OLED_Init(void)
{
	SPI_Shutdown(OLED_SPI);
	if (SPI_Init(OLED_SPI, 0, SPI_SPEED) != E_NO_ERROR)
	{
	    printf("Error configuring SPI!\n");
	    mxc_delay(MXC_DELAY_SEC(1));
	}
	
	OLED_RST_CLR();
	mxc_delay(MXC_DELAY_MSEC(100));
	OLED_RST_SET();
	/* 以下为历程中提供的初始化命令,略 */
}

 

修改向OLED写入数据的函数:

spi_req_t oledreq =
{
	.tx_data = NULL,
	.rx_data = NULL,
	.ssel_pol = SPI_POL_LOW,
	.len = 1,
	.bits = 8,
	.width = SPI17Y_WIDTH_1,  // NOT applicable to SPI1A and SPI1B, value ignored
	.ssel = 0,               // NOT applicable to SPI1A and SPI1B, value ignored
	.deass = 1,              // NOT applicable to SPI1A and SPI1B, value ignored
	.tx_num = 0,
	.rx_num = 0,
	.callback = NULL,
};
void OLED_WR_Byte(uint8_t data,uint8_t cmd)
{	
	if(cmd)
        OLED_DC_SET();
    else
        OLED_DC_CLR();
	
	oledreq.tx_data = &data;
	SPI_MasterTrans(OLED_SPI, &oledreq);
	OLED_DC_SET();   	  
}

在主程序,可以通过如下代码完成OLED屏幕显示:

while (1) 
{
	OLED_Clear();
	/* 显示时间信息 */
	Oled_ShowTime();
	/* 显示温湿度信息 */
	sprintf(string_todisplay, "T:%.2f H:%.2f", temperature / 1000.0f, humidity / 1000.0f);
	OLED_ShowString(20, 30, (uint8_t*)string_todisplay, 12);
	/* 显示倾角信息 */
	sprintf(string_todisplay, "X:%.1f Y:%.1f Z:%.1f", angle_x, angle_y, angle_z);
	OLED_ShowString(0, 45, (uint8_t*)string_todisplay, 12);
	OLED_Refresh();
	mxc_delay(MXC_DELAY_SEC(1));
}

 

时间信息的显示Oled_ShowTime(),借助上一节中GetNowTime()即可:

const char *weekStr[7] = 
{
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
void Oled_ShowTime()
{
    char ch[30];
	GetNowTime(&nowTime);
	/* YYYY-MM-DD WEEK*/
	sprintf(ch, "%04d-%02d-%02d %s", nowTime.year, nowTime.month, nowTime.day, weekStr[nowTime.weekday]);
    OLED_ShowString(20, 0, (uint8_t*)ch, 12);
    /* HH-MM-SS */
	sprintf(ch, "%02d:%02d:%02d", nowTime.hour, nowTime.minute, nowTime.second);
	OLED_ShowString(40, 15, (uint8_t*)ch, 12);
}

 

自此,完成所有信息的显示,时间每秒刷新,温湿度、倾角信息每分钟刷新的功能。最后添加一个按键,以即刻刷新温湿度、倾角信息,并在按键按下后,LED灯亮1秒。

对LED、PB(push button)初始化:

extern int buttonPressed;
void pbHandler(void *pb)
{
	buttonPressed = 1;
}
void gpio_init(void)
{
	LED_Init();
	PB_Init();
	PB_RegisterCallback(0, pbHandler);
	PB_IntEnable(0);
}

 

当每次更新温湿度、倾角信息时(调用DataUpdate()),调用LED_On(0)即可;在主程序while(1)中,每次均关灯,并判断按键是否被按下:

while(1) 
{
	LED_Off(0);
	if(buttonPressed)
	{
		DataUpdate();
		buttonPressed = 0;
	}
}

 

写成如此判断的缺陷是,并非每次按下均能立即判断是否被按下,因为主循环内存在1秒延时函数;写成按键中断的形式可以立即响应,但不知为何SHT40的I2C读数据会出错。另,写成如下形式也可判断,或许由于按键未消抖,导致未及时更新的次数更多。

if(PB_Get(0))
{
	DataUpdate();
}

 

功能演示结果及说明

初始化

设定初始时间2021-11-27 23:56:00,OLED显示如下:

初始化OLED显示

此时已经读取了温湿度及倾角值并显示在OLED屏上。

按键即刻更新

按下按键,OLED变化如下两图:

按下前

按下后

可以发现,按下时LED灯亮,温湿度、倾角信息有变化。

每分钟自动更新

由于设置初始时间为00秒,每分钟自动更新一次数值,如下两图:

更新前

更新后

由于程序存在延迟,且读取数据时的I2C读写需要时间,会出现OLED跳秒显示的情况,但RTC时间仍是准的。

项目总结 & 心得体会

该项目可实现功能如下:

  1. OLED屏幕显示年月日星期时分秒、温湿度信息、倾角信息;
  2. 可采集温湿度、倾角数据,自动每分钟更新(获取)一次,每次更新LED亮1秒;
  3. 用芯片内部RTC计算万年历,每秒更新时间至屏幕,及触发每分钟更新的功能(中断处理);
  4. 配有按键,按下提示灯亮1秒,并立刻更新温湿度、倾角数据至屏幕;
  5. *初始时间只能通过烧写时写入,重新上电则时间重置;
  6. 获取的温湿度、三轴加速度、倾角数据会通过串口打印。

心得体会:

  1. 此次使用的MAX32660麻雀虽小,五脏俱全。遗憾是GPIO口太少,最后想加一个串口以更新时间初始值的功能但没有多余的UART可以使用;
  2. ADXL345本想使用中断以检测自由落体的,但是可能设置有问题,中断出不来,只能实现测量倾角;
  3. 后续希望将屏幕换成I2C通信的,这样可以多一个串口资源,并希望利用RTC实现一些闹钟、低功耗、自动唤醒等场景。
附件下载
fastbond_demo.hex
HEX文件直接烧录
FASTBOND_DEMO-1128.rar
完整的工程文件,基于Keil5,MAX32660
团队介绍
北京交通大学 电子信息工程学院 电子科学与技术专业就读
团队成员
葉SiR
二次元の开发者;👉 GitHub: https://github.com/KafCoppelia
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号