模拟时钟
AUTHOR:奈奎斯特不稳定
本文档对应硬禾学堂寒假一起练(5)
项目完成功能:
-
时间显示功能,能显示单片机中记录的时间。其时间可以用上位机的特定的指令修改,格式如下
"time:y%M%md%dYh%Hm%Ms%S"
可以显示时间可以精确到秒,支持多次修改。
-
整点报时功能,具体响多少下,取决于给他的延时时间。KEY1按键关闭\开启蜂鸣器报警,左上角会显示其状态。
-
根据重力调整时钟位置,整个时钟的会随着器件的转动而发生相应的改变。
注:由于数字是由字库显示的。所以数字和字符的朝向不能改变。
-
显示考研剩余天数。因为我是2022考研狗。所以需要时刻显示天数提醒我自己。按键4可以控制其开闭。
-
SD卡图片显示函数。(需要提前将数组转化存入SD卡)不演示。
-
按键对应不同的功能:
-
控制报时是否有效
-
关闭背光
-
开启背光
-
显示剩余考研天数(前提需要矫正时间)
注:按键功能定义不能太长。程序可能会跑飞。
-
所用板子资源介绍:
-
定时器2、3、4
-
LCD——SPI协议
-
按键——外部中断
-
MPU6050——I2C协议
-
SD卡——硬件SPI及FATF系统
按键:
按键是又硬件下拉的,所以初始化的时候只需要保持默认就行了。
蜂鸣器:
打算让他响1s,再停一秒的。结果发现好像就接上了一下,之后就没声了,需要耳贴近才能听到。后来我才意识到,这应该就是传说中的无源蜂鸣器。
那只能再改了,还好用hal库的话比较方便。在群友的建议下。我打算使用4K的PWM使蜂鸣器响。
在修改的时候我好像悲催的发现PC10引脚好像不能直接输出PWM,我的天。
由于程序资源占用太多,导致正常的延迟函数已经不能满足要求,所以只能在中断里操作了。引脚的模拟PWM频率在4KHz。
void MX_TIM4_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim4.Instance = TIM4;
htim4.Init.Prescaler = 72;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 250;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
LCD刷新的问题
使用缓存数组,来显示图片,这样显示图片的效率能提升一倍。但是,发现如果需要准备缓冲数组,发现stm32的内存好像不足以支撑,也可能是我的操作方法的问题。所以我初始化了SD卡来拓展容量。可能使用SPI通讯的缘故,我发现随机写入SD卡数组的耗时实在是太长了。所以果断放弃了SD卡存放缓冲数组,改用局部刷新的方法
关于时钟延迟初始化
经过群友提醒,我发现这个板子由于是旁路时钟,所以会出现上电瞬间,时钟初始化失败。
直接将HSE的timout改大一点就行
这里的数值改成1000U。上电后没有初始化失败的情况了。
MPU6050
只需要将I2C硬件初始化。然后按照数据手册,发送相对应的初始化命令就行了。本项目要求的精度不需要很高,按照默认的就行。项目的重点,是利用重力加速度在XY轴的分力的夹角来确定方向的。
网上有很多关于mpu6050姿态解算的案例。这里就不多做赘述。
G_direction=-atan2(ax,ay);
上位机/下位机
现在加入下位机。之前有写过类似的,所以写起来应该会很快。现在加个串口接收,用DMA。
上位机发送代码格式请看下面的代码。
"time:y%M%md%dYh%Hm%Ms%S"
char *datare;
char time_begin[] = "time:";
char s_hour[] = "hour:";
char s_minument[] = "minument:";
char s_sec[] = "sec:";
int fresh_temp_sec;
void fresh_s_time(char *temp)
{
for(fresh_temp_sec = -1; fresh_temp_sec<2; fresh_temp_sec++)
RoundClock_CLR_ALL(WatchHour,WatchMiniute,WatchSec+fresh_temp_sec);
while(*temp != '\0')
{
switch(*temp++)
{
case 'h':
WatchHour = (*temp-'0')*10 + (*(temp+1)-'0');
temp+=2;
break;
case 'm':
WatchMiniute = (*temp-'0')*10 + (*(temp+1)-'0');
temp+=2;
break;
case 's':
WatchSec = (*temp-'0')*10 + (*(temp+1)-'0');
temp+=2;
break;
case 'y':
watch_date.years = (*temp-'0')*10 + (*(temp+1)-'0');
temp+=2;
break;
case 'M': //月份
watch_date.months = (*temp-'0')*10 + (*(temp+1)-'0');
temp+=2;
break;
case 'd':
watch_date.days = (*temp-'0')*10 + (*(temp+1)-'0');
temp+=2;
break;
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
int i=0,m;
m=strlen(time_begin);
if(huart->Instance == USART2)
{
datare = (char *)rx_buffer;
for(i=0; i<rx_len-m; i++)
{
if(strncmp(datare+i,time_begin,m)==0) //找到头
{
datare+=m+i;//取有效数据
fresh_s_time(datare);
}
}
memset(rx_buffer,0,sizeof(rx_buffer));//只能在中断里做,因为时间资源被占用完了。
HAL_UART_Receive_DMA(&huart2,rx_buffer,BUFFER_SIZE);//重新打开DMA接收
}
}
以上是我完成项目踩到的主要的坑。省略了很多部分。具体部分请看我的博客
原文非常冗长,读起来可能会有一定的障碍。