AUTHOR:奈奎斯特不稳定
代码下载
本文档对应硬禾学堂寒假一起练(5)
由于到货比较慢。所以之前我就用别的器件代替试做了一下第一个,重力时钟。等到到货的时候我已经差不多做出来了。所以我现在非常清楚需要哪些资源。
总共要初始化的部分:一个I2C总线、一个SPI总线、两个定时器、四个GPIO初始化上拉、一个串口、蜂鸣器
还是挺简单的哈。
大一寒假开始学长让我们自学32,当作进实验室的考核。进了实验室后,也都是凭着兴趣在学。现在大三了。感觉这个板子上的还好。有些时候遇到那种看不到的坑。那是最难受的。整一个月都整不出来的那种。
MPU6050及BH1750 | 引脚 |
---|---|
SDA | PB10 |
SCL | PB11 |
我喜欢用定时器中断完成我需要的任务
另外一个定时器提供1ms的标准时钟,用于更新实时时间。
用于按键的闲杂功能。
这几个引脚在硬件上都被上拉了。所以,理论上是不需要初始化上拉的。但是我还是决定,初始化上拉一下。然后低电平检测。
按键 | 引脚 |
---|---|
KEY1 | PC9 |
KEY2 | PA8 |
KEY3 | PA11 |
KEY4 | PA12 |
LCD | 引脚 |
---|---|
LCD_ BL | PC12 |
LCD_ SCL | PB4 |
LCD_ SDA | PB5 |
LCD_ DC | PD2 |
LCD_ RST | PB6 |
LCD_ CS | PB3 |
说实话,我是没有用过LCD显示屏的,之前的项目中也是用的OLED代替LCD显示屏。所以,我预测在LCD显示屏上开发的是时间将会最久。
使用DMA通讯,用DMA的话,占的时钟资源比较少。主要是打算使用串口,修改时钟数据。为此我特地写了一个小型的下位机。上位机用python获取当前的时间。然后通过串口发送到单片机。这样改的时间就会比较准确。用按键改时间实在是太鸡肋了。
硬禾给的下载器上有一路的USART,那就不用多余的线,就非常完美。
PA3:RX
PA2:TX
用于整点报时。PC10.我模电学的不是很扎实。这个电路应该是给低电平蜂鸣器不响。给高电平蜂鸣器响吧。
那就下拉输出初始化。
对我这个方案有些不理解的可以看一下我之前的心得,也在这个文件夹里。
首先,先初始化所有的资源。用CUBEMX就是这么自信。不会出错。定时器和串口都要中断使能。
常规的72M
定时器2:7200的分频,记100个数。就是10ms,优先级设为一,其他都不变
定时器3:7200的分频,记10个数。就是标准的1ms
初始化为异步串口,其他不用动,添加DMA,RX,TX都要添加。以防之后有可能要用到。
初始化就行。上不上拉的都无所谓了。
初始化就完事了,电路中本身就下拉了。
这里是我最不熟练的部分了,所以初始化起来。我打算小心再小心。首先上网搜资料。嘿嘿。
在网上搜了一圈资料之后果然没有现成的。这个SPI就先不初始化了。以后再说。测试一下各个功能是否正常。
打算让他响1s,再停一秒的。结果发现好像就接上了一下。之后就没声了。而且需要耳贴近才能听到。后来我才意识到。这应该就是传说中的无源蜂鸣器。
那只能再改了,还好用hal库的话比较方便。在群友的建议下。我打算使用4K的PWM使蜂鸣器响。
在修改的时候我好像悲催的发现PC10引脚好像不能直接输出PWM。我的天。
现在我能想到的是利用定时器四来翻转IO达到输出PWM的目的。哎呀,干嘛这么麻烦。用延时函数凑合得了。
经过了一夜的纠结后,我还是打算用TIM4的定时器中断来产生PWM波脉冲。打算利用他的PWM模块产生的中断。进入来操作IO口。
搜罗了网上的资料之后,发现无源蜂鸣器的最佳频率为3K-4K之间。取了一个裕度后,我打算将PWM的频率设置在5K左右。即0.2MS
利用公式:Tout = ((ARR + 1)*(PSC + 1)) / Tclk
非常容易计算出来。Tout = 0.2 *10-3s、ARR+1 = 2000、Tclk = 72 *106、 PSC+1 = 7(算出来是7.2由于不能取小数,所以我取7)
为了贪图方便。我就把蜂鸣器的操作都放在了主函数里面,不高兴再开一个文件了。
刚刚,我突然发现我好像想多了,我想着改他的占空比了。这种无源蜂鸣器干嘛改占空比啊。直接在中断服务函数中翻转电平不久好了?
所以,最终的方案啊,不分频,直接修改ARR来改变他的频率。
至此,蜂鸣器验证完毕。
在网上找了好多资料,学习了DRAM等一些莫名其妙的操作后,突然想起来。原理图上好像有对应的型号。将型号输入到某宝里说不定能搜到对应的资料。
结果真搜到了一个对应的资料。不知道效果怎么样。
看了一圈例程,没有RBT的例程,真的非常可惜,随便打开一个看看。非常轻松的读出。程序用的是软件SPI。先在cube中初始化。
经过了半天的修改移植,总算是能显示图片了。但是文字还是不能显示。不过能显示图片就说明能打点了。那就好办了。先把我之前拿到的OLED的API拿出来修改一下。
改了半天,发现要把整个工程都改过来太麻烦了。我打算创建以LCD_API。文件里面包含了所有的需要用到的函数。
经过了一个晚上的奋斗。总算使能把时钟显示出来了。明天找个时间。把表盘大小和数字改一下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8T6CN2bz-1611207720576)(https://gitee.com/root__locus/pic_cloud/raw/master/temp.jpg)]
那最常见的方法,用空间换时间呗。
现在是可以成功显示时钟了。但是也遇到了一个问题。就是LCD的刷新率太慢。导致画面有停顿感。所以我打算用显示图片的方法。来些屏幕。但是我现在对LCD的数组不是很清楚。不知道他的数组是怎么来显示颜色的。所以主要任务是把数组整明白。
先用Image2lcd软件提取一副240*240的图像试一下。看看提取出来的数组会有多大。
这里随便引入一张图片,
在photoshop中转换成240*240的彩色图片。
将图片在LCD中过一遍:
const unsigned char gImage_LCD_TEMP[115200]
出来了115200大的数组。也就是这个数组里面有115200个数据。
用计算机算一下:240 * 240 * 2 = 115200 ,也就是说。LCD屏幕一个像素点占两个char型。也就是16位。
。。。啊,感觉好复杂。要写算法的话。啊啊啊。
现在先试一下做个一8*8的黑白图片
就像这样
转换后:
const unsigned char gImage_xuexi[128] = { /* 0X00,0X10,0X08,0X00,0X08,0X00,0X01,0X1B, */
0X00,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0X00,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0X00,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0X00,0X00,
};
可以很清楚得窥见。黑色为0x00,白色为0xff
在我将一张图片塞进程序里之后我突然发现。stm32好像没有那么多的空间塞一张图片。
仔细算一下,115200*8 = 921600 = 1110 0001 0000 0000 0000b说不定真超了。
不得不说。这坑啊。哎。
现在得解决方案有两个:
做一个sd卡的外设.
在原有程序的基础上修修补补
我选择后者。因为懒。我打算将会旋转的部分刷新。也就是在刷新之前,填成黑色。
看起来这个方案失败了。果然不出所料的在闪。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FaXILq6o-1611207720578)(https://gitee.com/root__locus/pic_cloud/raw/master/IMG_20210114_204649.jpg)]
看起来不得不使用三位数组来完成这个任务了。
看了一下三维数组的介绍。发现这里图形数组的用法和三维图形如出一辙。所以我打算多花点时间使用方案2.
看了一下红色是 0xF800 这里稍微记一下。一会儿会用到。
好了初始化SD卡。还好我之前做过的BADAPPLE有用到SD卡。
不对!!!这个RBT没有SDIO接口。我靠真的到处都是坑啊。
在经历了一天的挣扎后。总算,时钟是有点象样了。但是。这个时钟不能旋转。也就是说。不得不用缓存数组。而要用缓存数组。则不得不用SD卡。
我参照的是b站的一个视频
那有了SD卡这个外部存储之后做事就变得简单了。
讲真,这个大小用串口发送数据。的话要1/8秒才能完成。换句话说。就是8帧的刷新率。就这刷新率。也最多能当个钟用了
就是最基本的打点函数。
有关SD卡的读写操作的技巧可以看我的BADAPPLE
里面有我关于图片数组的见解。我在这里简单重复一下。现在需要准备一个三维数组。分别是长度、宽度和深度(颜色)。在c语言中。如果要定义对数组处理的函数。就需要弄清楚指针和数组的关系。
以二位数组为例:如果要处理char类型二位数组。可以定义一个指向char的指针。然后将二位数组得到首地址赋给指针。然后对指针进行操作。这样做可以,但是不严谨,而且繁琐:首先二位数组的首地址是指向数组的指针,而不是指向char的指针。直接赋值并不严谨。其次,需要知道数组和指针的增量关系然后构筑算法来解决这个问题。繁琐。
也可以使用二重指针。定义一个固定深度的指针。指向数组指针。到时候取用的时候只需要按照数组的表达方式就可以取到对应的值。这种方法的缺点是。你必须知道你要处理的指针的深度。
还有一些别的赋值方法。这里就不介绍了。参照上面的图片看吧。
在函数的多维数组中,第一括号知识表明这是一个指针,里面的内容可以省略外,后面的括号里的内容都不能省略。
相信大叫都知道文件这种东西。但是要仔细说明文件的本质,这还是有点难度的。所以在这之前,先要明白文件是什么。
我是这样想的,以3行为一个单位。循环读取80次就是240行。每一次循环把3行的数据打印在屏幕上。每一行的数据是240*2.一次读取
240 * 3 * 2 = 1440为1k多的数据。还是可以接受的。
现在打算把图片的数据存在SD卡中。然后用f_read读取数据。
现在能隐约看到原图了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wSjonufc-1611207720580)(https://gitee.com/root__locus/pic_cloud/raw/master/LCD%E5%9B%BE%E7%89%87%E5%A4%B1%E7%9C%9F.jpg)]
但是图片好像是有点彩色上的失真。
和原图对比一下
[外链图片转存失败,源站可能有防盗image-20210114163555401]!链机制,建议将https://上传(blom.csFnimg.cn/im6_convert/c42bdGgB12801d740f23af18214e0eb004.pn4339)(https://img-log.csdnimg.cn/img_convert/c42b12801d740f23af1498214e0eb004.png)]
我换了一种编写文件的方式。发现多少有丢包的现象不知道是为什么
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PmVCwhrr-1611207720582)(https://gitee.com/root__locus/pic_cloud/raw/master/1610972552093.jpg)]
关于操作SD卡中数组的事情。我就直接放在了LCD.c这个文件里了。
SD卡显示图片失败。我把原因归结于:刷新太耗费时间。导致SD卡指针忙碌。
刚刚我测试了一下,结果发现。如果把数组放在SD卡里面读写实在是太慢了。整个过程有27秒。这个方法果断放弃。
经过实验证明。用这个LCD屏不闪是不可能的了所以我要做的,就是尽量让他不要闪
由于我使用的是局部刷新。如果刷新速度太快。会导致画面闪烁。而如果刷新太慢又会导致有小点残留在画面当中。这是我不能仍受的。所以我打算把那一块用矩形填起来。
要按照时间的规律去刷新的话。也能节省一部分资源。不能按照时间刷新了看来。只能按照坐标刷新。对应的分针在哪个块就刷新哪快。
经过群友提醒,我发现这个板子由于是旁路时钟。所以会出现上电瞬间。时钟初始化失败。
直接将HSE的timout改大一点就行
这里的数值改成1000U。上电后没有初始化失败的情况了。
现在加入下位机。之前有写过类似的。所以写起来应该会很快。现在加个串口接收。用DMA。
上位机发送代码格式请看下图
头文字为 :time:
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接收
}
}
由于程序资源占用太多。导致正常的延迟函数已经不能满足要求。所以只能在中断里操作了。tim4为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();
}
}
所以延时1s为4000个数
在开发的过程中,发现由于过程工程所使用的时钟资源太多了。导致WHILE中的时钟资源已经很少了。所以我打算更改按键扫描的策略。改用外部中断。
还有好多以前在while里面能解决的问题。都只能放在中断里面了。资源消耗太多。
时间显示功能。能显示单片机中记录的时间。其时间可以用上位机的特定的指令修改。格式如下
"time:y%M%md%dYh%Hm%Ms%S"
可以显示时间可以精确到秒。支持多次修改。
整点报时功能。具体就是到了几点就会响几下。可以通过按键来取消报点功能。报点功能是否有效。屏幕左上角会显示。
根据重力调整时钟位置。注:由于数字是由字库显示的。所以数字和字符的朝向不能改变。
整个时钟会随着重力的改变而改变方向。
显示考研剩余天数。因为我是2022考研狗。所以需要时刻显示天数提醒我自己。由于天数计算的函数没有设计完全。题目中也没有要求。所以就不显示了。
SD卡图片显示函数。(需要提前将数组转化存入SD卡)不演示
按键对应不同的功能:
控制报时是否有效
关闭背光
开启背光
未定义
注:按键功能定义不能太长。程序可能会跑飞。
由于要保证屏幕的刷新率和反应速度,很多地方我都进行了简化处理。如果想加入更多的功能。比如卡尔曼滤波。可以考虑减小屏幕的刷新率。
定时器2、3、4
LCD——SPI协议
按键——外部中断
MPU6050——I2C协议
可以显示时间可以精确到秒。支持多次修改。
整点报时功能。具体就是到了几点就会响几下。可以通过按键来取消报点功能。报点功能是否有效。屏幕左上角会显示。
根据重力调整时钟位置。注:由于数字是由字库显示的。所以数字和字符的朝向不能改变。
整个时钟会随着重力的改变而改变方向。
显示考研剩余天数。因为我是2022考研狗。所以需要时刻显示天数提醒我自己。由于天数计算的函数没有设计完全。题目中也没有要求。所以就不显示了。
SD卡图片显示函数。(需要提前将数组转化存入SD卡)不演示
按键对应不同的功能:
控制报时是否有效
关闭背光
开启背光
未定义
注:按键功能定义不能太长。程序可能会跑飞。
由于要保证屏幕的刷新率和反应速度,很多地方我都进行了简化处理。如果想加入更多的功能。比如卡尔曼滤波。可以考虑减小屏幕的刷新率。