NWatch-Alarm闹钟功能实现


W25Q64初始化

W25Q64用来存储用户设置的闹钟值,开机的时候自动读取闹钟记录。

SPI初始化

使用CubeMx自动配置。
在这里插入图片描述

#define SPI1CS_ON	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET)
#define SPI1CS_OFF	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET)

static volatile bool gSPI1TxCplt = 0;
static volatile bool gSPI1RxCplt = 0;

static void SPI1WaitTxCplt(void)
{
    while(gSPI1TxCplt == 0);
    gSPI1TxCplt = 0;
}

static void SPI1WaitRxCplt(void)
{
    while(gSPI1RxCplt == 0);
    gSPI1RxCplt = 0;
}

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if(SPI1 == hspi->Instance)
    {
        gSPI1TxCplt = 1;
    }
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if(SPI1 == hspi->Instance)
    {
        gSPI1RxCplt = 1;
    }
}

static int SpiWrite(unsigned char *wbuf, unsigned int length){
	HAL_StatusTypeDef status = HAL_SPI_Transmit_DMA(&hspi1, wbuf, length);
    if(HAL_OK != status)    return -1;
	SPI1WaitTxCplt();
    
    return length;
}

static int SpiRead(unsigned char *rbuf, unsigned int length){
	HAL_StatusTypeDef status = HAL_SPI_Receive_DMA(&hspi1, rbuf, length);
    if(HAL_OK != status)    return -1;
	SPI1WaitRxCplt();
    
    return length;
}

W25Q初始化

使用SPI读取W25Q的ID验证通信的正确性

static unsigned int ReadID(void)
{    
    unsigned char cmd = 0x9F;   // W25X_JedecDeviceID
    unsigned char id[3] = {0};
    
    SPI1CS_ON;
    SpiWrite(&cmd,1);
    SpiRead(id, 3);
    SPI1CS_OFF;
    
    unsigned int ID = (id[0] << 16) | (id[1] << 8) | (id[2]);
    return ID;
}

void W25QInit(void){
    int ID = ReadID();
	if(ID != 0xEF4017)
		return;
	printf("ID is %d",ID);
}

int W25QPageWrite(uint32_t addr,unsigned char *buf,unsigned int length){

	unsigned char data[4];
	data[0] = 0x02;
	data[1] = (addr &0xFF0000)>>16;
	data[2] = (addr &0x00FF00)>>8;
	data[3] = (addr &0x0000FF);

	W25QWriteEnable();
	SPI1CS_ON;
	int ret = SpiWrite(data,4);
	if(ret < 0){
		SPI1CS_OFF;
	}

	ret = SpiWrite(buf,length);
	if(ret < 0){
		SPI1CS_OFF;
	}
	SPI1CS_OFF;

	WaitWrite();
	return (int)length;
}

int W25QRead(uint32_t addr,unsigned char *buf,unsigned int length){

	unsigned char data[4];
    data[0] = 0x03;
    data[1] = (addr &0xFF0000)>>16;
    data[2] = (addr &0x00FF00)>>8;
    data[3] = (addr &0x0000FF);
    
    SPI1CS_ON;
    int ret = SpiWrite(data,4);
	if(ret < 0){
		SPI1CS_OFF;
	}
	
    ret = SpiRead(buf,length);
    if(ret != length){
		SPI1CS_OFF;
	}
    SPI1CS_OFF;
    return (int)length;
}

void W25QEraseSector(uint32_t addr){

	unsigned char data[4];
	data[0] = 0x20;
    data[1] = (addr &0xFF0000)>>16;
    data[2] = (addr &0x00FF00)>>8;
    data[3] = (addr &0x0000FF);
	
	W25QWriteEnable();
	SPI1CS_ON;
	int ret = SpiWrite(data,4);
	if(ret < 0){
		SPI1CS_OFF;
	}
    SPI1CS_OFF;
	WaitWrite();
                                                                             }

AlARM模块初始化

设置闹钟界面的信息

/*
 * 描述:设置闹钟界面的页面加载函数
 * 参数:无  
 * 返回值:无	 
 */
static void AlarmLoader(uint8_t num){
	if(num < ALARM_COUNT-1)
	{
		alarm_s alarm2;
		if(setting.now == SETTING_NOW_NONE || num != Main_Menu.selected)
			Alarmget(num, &alarm2);
		else
			memcpy(&alarm2, &alarm, sizeof(alarm_s));
		AddAlarmBasic(num, &alarm2);
	}
	addExitOption();
}
/*
 * 描述:设置闹钟界面的确认函数
 * 参数:无  
 * 返回值:无	 
 */
static void AlarmSelect(){
	bool isExiting = exitSelected();
	GetMenupre(&alarmMenus);
	DoAction(isExiting);
}

/*
 * 描述:设置闹钟界面的按钮函数等菜单基本信息 
 * 参数:无  
 * 返回值:无	 
 */
void AlarmMenu(void){
        SetButtonFunc(menuUp, (bool (*)(void))AlarmSelect, menuDown);
		SetMenuFunc(preOption, AlarmSelect, nextOption,AlarmLoader);
		SetMenuInfo(ALARM_COUNT,MENU_TYPE_STR,PSTR(STR_ALARMMENU));
		SetMenuPre(&alarmMenus,AlarmMenu);
		AnimotionInto(NULL);
}

AlARM模块绘制Draw函数

alarm模块是STR菜单界面,更改界面的值通过状态机的方式逐个改变每个数据的值然后通过确认赋值,改变完所有数据值后保存到外部Flash中。

static void AlarmUp(void){
	setting.val++;
	if(setting.val > getMaxValForSetting())
		setting.val = 0;

	if(setting.now == SETTING_NOW_HOUR && appConfig.timeMode  == TIMEMODE_12HR && setting.val == 0)
		setting.val = 1;
}

static void AlarmDown(void){
	setting.val--;
	if(setting.now == SETTING_NOW_HOUR && appConfig.timeMode  == TIMEMODE_12HR && setting.val == 0)
		setting.val = 12;

	uint8_t max = getMaxValForSetting();
	if(setting.val > max)
		setting.val = max;
}

static void AlarmDraw(void){
	uint8_t w = 5;
	uint8_t x;
	char buff[4];
	buff[1] = 0x00;
	switch(setting.now)
	{
		case SETTING_NOW_EN:
			x = 6;
			w = 8;
			buff[0] = setting.val ? ALARM_SET_CHAR : ALARM_UNSET_CHAR;
			break;
		case SETTING_NOW_HOUR:
			x = 22;
			w = 16;
			break;
		case SETTING_NOW_MIN:
			x = 46;
			w = 16;
			break;
		case SETTING_NOW_AMPM:
			x = 62;
			w = 8;
			buff[0] = setting.val ? CHAR_PM : CHAR_AM;
			break;
		case SETTING_NOW_DAY_MON:
		case SETTING_NOW_DAY_TUE:
		case SETTING_NOW_DAY_WED:
		case SETTING_NOW_DAY_THUR:
		case SETTING_NOW_DAY_FRI:
		case SETTING_NOW_DAY_SAT:
		case SETTING_NOW_DAY_SUN:
			{
				uint8_t dow = setting.now - SETTING_NOW_DAY_MON;
				x = 70 + (8 * dow);
				w = 8;
				buff[0] = setting.val ? dowChars[dow] : '-';
			}
			break;
		default:
			return;
	}

	uint8_t y = 8 + ((Main_Menu.selected * 8) - (Main_Menu.scroll * 8));
	
	OledClearArea(x, y, w);
	
	if(setting.now == SETTING_NOW_HOUR || setting.now == SETTING_NOW_MIN)
		sprintf(buff, PSTR("%02hhu"), setting.val);

	OledShowStr(x,y,buff,OLED_8X8);
}

static void SelectedAlarm()
{
	static uint8_t dayBit;
	static bool wasPM;

	SetMenuFunc(AlarmUp, AlarmSelect, AlarmDown, AlarmLoader);
	Main_Menu.func.draw = AlarmDraw;

	switch(setting.now)
	{
		case SETTING_NOW_NONE:
			Alarmget(Main_Menu.selected, &alarm);
			setting.now = SETTING_NOW_EN;
			setting.val = alarm.enabled;
			break;
		case SETTING_NOW_EN:
			alarm.enabled = setting.val;
			setting.now = SETTING_NOW_HOUR;

			time_s time;
			time.hour = alarm.hour;
			time.ampm = CHAR_24;
			TimeSetMode(&time, appConfig.timeMode);

			setting.val = time.hour;
			
			wasPM = (alarm.hour >= 12);

			break;
		case SETTING_NOW_HOUR:
		{
			uint8_t hour = setting.val;
			uint8_t max = appConfig.timeMode == TIMEMODE_12HR ? 12 : 23;
			if(hour > max)
				hour = max;

			if(appConfig.timeMode  == TIMEMODE_12HR)
			{
				time_s time;
				time.hour = hour;
				time.ampm = wasPM ? CHAR_PM : CHAR_AM;
				TimeSetMode(&time, TIMEMODE_24HR);
				hour = time.hour;
			}

			alarm.hour = hour;

			setting.now = SETTING_NOW_MIN;
			setting.val = alarm.min;
		}
			break;
		case SETTING_NOW_MIN:
			alarm.min = setting.val;
			if(appConfig.timeMode  == TIMEMODE_12HR)
			{
				setting.now = SETTING_NOW_AMPM;
				setting.val = wasPM;
			}
			else
			{
				setting.now = SETTING_NOW_DAY_MON;
				setting.val = alarm.mon;
			}
			dayBit = 0;
			break;
		case SETTING_NOW_AMPM:
		{
			time_s time;
			time.hour = alarm.hour;
			time.ampm = setting.val ? CHAR_PM : CHAR_AM;
			TimeSetMode(&time, TIMEMODE_24HR);
			alarm.hour = time.hour;
			setting.now = SETTING_NOW_DAY_MON;
			setting.val = alarm.mon;
		}
			break;
		case SETTING_NOW_DAY_MON:
		case SETTING_NOW_DAY_TUE:
		case SETTING_NOW_DAY_WED:
		case SETTING_NOW_DAY_THUR:
		case SETTING_NOW_DAY_FRI:
		case SETTING_NOW_DAY_SAT:
		case SETTING_NOW_DAY_SUN:
			if(setting.val)
				alarm.days |= _BV(dayBit);
			else
				alarm.days &= ~_BV(dayBit);
			dayBit++;
			setting.val = (bool)(alarm.days & _BV(dayBit));
			if(setting.now != SETTING_NOW_DAY_SUN)
				setting.now++;
			else
			{
				AlarmSave(Main_Menu.selected, &alarm);
				setting.now = SETTING_NOW_NONE;
				SetMenuFunc(preOption, AlarmSelect, nextOption,AlarmLoader);
				Main_Menu.func.draw = NULL;
			}
			break;
		default:
			break;
	}
}

AlARM的更新函数

如果当前时间是闹钟设置的时间,则进行相应。改变当前页面绘制函数称为闹钟响后的绘制函数。


void AlarmUpdate(void){
	bool wasGoingOff = alarmSetOff;
	bool alarmNow = goingOff();
  	if(alarmSetOff)
	{
		if(alarmNow)
		{
			if(wasGoingOff != alarmSetOff)
			{
				oldDrawFunc = SetDrawFunc(AlarmDraw);
				oldBtn1Func = SetButtonSignedFunc(BTN1, NULL);
				oldBtn2Func = SetButtonSignedFunc(BTN2, (bool (*)(void))AlarmStop);
				oldBtn3Func = SetButtonSignedFunc(BTN3, NULL);
				BuzzerPlay(tuneAlarm,VOL_ALARM,PRIO_ALARM);
			}
			if(!LedFlashing())
			{
				static led_t ledFlash = LEDGREEN;
				ledFlash = (ledFlash == LEDGREEN) ? LEDRED : LEDGREEN;
				LedFlash(ledFlash, 150);
			}
		}
		else
			AlarmStop();
	}
}

static void AlarmDraw(void){
	if((uint8_t)xTaskGetTickCount() < 128)
		OledShowImage(16, 16, 32, 32, &MAIN_MUNE[3][0], 0);
	
	// Draw time
	char buff[8];
	sprintf(buff, PSTR(TIME_FORMAT_SMALL), timeDate.time.hour, timeDate.time.mins, timeDate.time.ampm);
	OledShowStr(60, 20,buff,OLED_8X8);

	// Draw day
	strcpy(buff, days[timeDate.date.day]);
	OledShowStr(68,36,buff,OLED_8X8);

}

AlARM中最重要的函数

每次时间或者闹钟响后,更新最近一个闹钟。通过计算本周开始到当前闹钟时间总共过去多少分钟,最少的就是最近一个闹钟。

void GetNextAlarm(void){
	uint8_t next = NOALARM;
	uint32_t nextTime = (uint32_t)UINT_MAX;

	// Make sure time is in 24 hour mode
	time_s timeNow;
	timeNow.hour = timeDate.time.hour;
	timeNow.ampm = timeDate.time.ampm;
	TimeSetMode(&timeNow, TIMEMODE_24HR);

	// Now in minutes from start of week
	uint32_t now = toMinutes(timeNow.hour, timeDate.time.mins + 1, timeDate.date.day);

	// Loop through alarms
	for(uint8_t i = 0;i <ALARM_COUNT; i++)
	{
		// Get alarm data
		alarm_s alarm;
		Alarmget(i, &alarm);

		// Not enabled
		if(!alarm.enabled)
			continue;

		// Loop through days
		for(uint8_t d = 0;d <7; d++)
		{
			// Day not enabled
			if(!AlarmDayEnabled(alarm.days, d))
				continue;

			// Alarm time in minutes from start of week
			uint32_t alarmTime = toMinutes(alarm.hour, alarm.min, d);

			// Minutes to alarm
			int timeTo = alarmTime - now;

			// Negative result, must mean alarm time is earlier in the week than now, add a weeks time
			if(timeTo < 0)
				timeTo += ((6*1440) + (23*60) + 59); // 10079

			// Is minutes to alarm less than the last minutes to alarm?
			if((uint32_t)timeTo < nextTime)
			{
				// This is our next alarm
				nextTime = timeTo;
				next = i;
			}
		}
	}
	// Set next alarm
	nextAlarm = next;
}
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

葛霸霸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值