【stm32】智能时钟开发流程带LCD显示

2 篇文章 0 订阅

在开始项目之前,先列一份方案

AUTHOR:奈奎斯特不稳定

代码下载

本文档对应硬禾学堂寒假一起练(5)

由于到货比较慢。所以之前我就用别的器件代替试做了一下第一个,重力时钟。等到到货的时候我已经差不多做出来了。所以我现在非常清楚需要哪些资源。

总共要初始化的部分:一个I2C总线、一个SPI总线、两个定时器、四个GPIO初始化上拉、一个串口、蜂鸣器

还是挺简单的哈。

大一寒假开始学长让我们自学32,当作进实验室的考核。进了实验室后,也都是凭着兴趣在学。现在大三了。感觉这个板子上的还好。有些时候遇到那种看不到的坑。那是最难受的。整一个月都整不出来的那种。

与mpu6050通讯的总线I2C

MPU6050以及BH1750(I2C)

MPU6050及BH1750引脚
SDAPB10
SCLPB11

两个定时器:

我喜欢用定时器中断完成我需要的任务

另外一个定时器提供1ms的标准时钟,用于更新实时时间。

四个GPIO的初始化

用于按键的闲杂功能。

这几个引脚在硬件上都被上拉了。所以,理论上是不需要初始化上拉的。但是我还是决定,初始化上拉一下。然后低电平检测。

KEYS:(这个按键分布真是一点也不友好啊)

按键引脚
KEY1PC9
KEY2PA8
KEY3PA11
KEY4PA12

LCD显示屏(初始化SPI总线)

LCD接口引脚:(SPI)

LCD引脚
LCD_ BLPC12
LCD_ SCLPB4
LCD_ SDAPB5
LCD_ DCPD2
LCD_ RSTPB6
LCD_ CSPB3

说实话,我是没有用过LCD显示屏的,之前的项目中也是用的OLED代替LCD显示屏。所以,我预测在LCD显示屏上开发的是时间将会最久。

串口(USART2)

使用DMA通讯,用DMA的话,占的时钟资源比较少。主要是打算使用串口,修改时钟数据。为此我特地写了一个小型的下位机。上位机用python获取当前的时间。然后通过串口发送到单片机。这样改的时间就会比较准确。用按键改时间实在是太鸡肋了。

硬禾给的下载器上有一路的USART,那就不用多余的线,就非常完美。

PA3:RX

PA2:TX

蜂鸣器(PC10)

image-20210112190441903

用于整点报时。PC10.我模电学的不是很扎实。这个电路应该是给低电平蜂鸣器不响。给高电平蜂鸣器响吧。

那就下拉输出初始化。

对我这个方案有些不理解的可以看一下我之前的心得,也在这个文件夹里。

好了,方案已经出来了,现在开干。

首先,先初始化所有的资源。用CUBEMX就是这么自信。不会出错。定时器和串口都要中断使能。

时钟

常规的72M

image-20210112183729684

定时器

定时器2:7200的分频,记100个数。就是10ms,优先级设为一,其他都不变

定时器3:7200的分频,记10个数。就是标准的1ms

I2C2总线

串口初始化

初始化为异步串口,其他不用动,添加DMA,RX,TX都要添加。以防之后有可能要用到。

四个GPIO

初始化就行。上不上拉的都无所谓了。

蜂鸣器

初始化就完事了,电路中本身就下拉了。

SPI初始化

这里是我最不熟练的部分了,所以初始化起来。我打算小心再小心。首先上网搜资料。嘿嘿。

在网上搜了一圈资料之后果然没有现成的。这个SPI就先不初始化了。以后再说。测试一下各个功能是否正常。

模块验证

mpu6050验证通过

串口验证通过

蜂鸣器

打算让他响1s,再停一秒的。结果发现好像就接上了一下。之后就没声了。而且需要耳贴近才能听到。后来我才意识到。这应该就是传说中的无源蜂鸣器。

image-20210113110831754

那只能再改了,还好用hal库的话比较方便。在群友的建议下。我打算使用4K的PWM使蜂鸣器响。

在修改的时候我好像悲催的发现PC10引脚好像不能直接输出PWM。我的天。

image-20210113110841631

现在我能想到的是利用定时器四来翻转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来改变他的频率。

至此,蜂鸣器验证完毕。

LCD屏幕验证

在网上找了好多资料,学习了DRAM等一些莫名其妙的操作后,突然想起来。原理图上好像有对应的型号。将型号输入到某宝里说不定能搜到对应的资料。

image-20210113130215386

结果真搜到了一个对应的资料。不知道效果怎么样。

image-20210113130353357

看了一圈例程,没有RBT的例程,真的非常可惜,随便打开一个看看。非常轻松的读出。程序用的是软件SPI。先在cube中初始化。

经过了半天的修改移植,总算是能显示图片了。但是文字还是不能显示。不过能显示图片就说明能打点了。那就好办了。先把我之前拿到的OLED的API拿出来修改一下。

改了半天,发现要把整个工程都改过来太麻烦了。我打算创建以LCD_API。文件里面包含了所有的需要用到的函数。

经过了一个晚上的奋斗。总算使能把时钟显示出来了。明天找个时间。把表盘大小和数字改一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8T6CN2bz-1611207720576)(https://gitee.com/root__locus/pic_cloud/raw/master/temp.jpg)]

遇到的问题2(刷新太慢)

那最常见的方法,用空间换时间呗。

现在是可以成功显示时钟了。但是也遇到了一个问题。就是LCD的刷新率太慢。导致画面有停顿感。所以我打算用显示图片的方法。来些屏幕。但是我现在对LCD的数组不是很清楚。不知道他的数组是怎么来显示颜色的。所以主要任务是把数组整明白。

先用Image2lcd软件提取一副240*240的图像试一下。看看提取出来的数组会有多大。

这里随便引入一张图片,

在photoshop中转换成240*240的彩色图片。

image-20210114163555401

将图片在LCD中过一遍:

const unsigned char gImage_LCD_TEMP[115200]

出来了115200大的数组。也就是这个数组里面有115200个数据。

用计算机算一下:240 * 240 * 2 = 115200 ,也就是说。LCD屏幕一个像素点占两个char型。也就是16位。

。。。啊,感觉好复杂。要写算法的话。啊啊啊。

现在先试一下做个一8*8的黑白图片

就像这样

image-20210114200600943

转换后:

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好像没有那么多的空间塞一张图片。

image-20210114201229276

仔细算一下,115200*8 = 921600 = 1110 0001 0000 0000 0000b说不定真超了。

不得不说。这坑啊。哎。

现在得解决方案有两个:

  1. 做一个sd卡的外设.

  2. 在原有程序的基础上修修补补

    我选择后者。因为懒。我打算将会旋转的部分刷新。也就是在刷新之前,填成黑色。

看起来这个方案失败了。果然不出所料的在闪。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FaXILq6o-1611207720578)(https://gitee.com/root__locus/pic_cloud/raw/master/IMG_20210114_204649.jpg)]

看起来不得不使用三位数组来完成这个任务了。

看了一下三维数组的介绍。发现这里图形数组的用法和三维图形如出一辙。所以我打算多花点时间使用方案2.

看了一下红色是 0xF800 这里稍微记一下。一会儿会用到。

好了初始化SD卡。还好我之前做过的BADAPPLE有用到SD卡。

不对!!!这个RBT没有SDIO接口。我靠真的到处都是坑啊。

在经历了一天的挣扎后。总算,时钟是有点象样了。但是。这个时钟不能旋转。也就是说。不得不用缓存数组。而要用缓存数组。则不得不用SD卡。

经历了几天的挣扎。我还是决定使用SD卡。

我参照的是b站的一个视频

image-20210117141307909

那有了SD卡这个外部存储之后做事就变得简单了。

先写一个图片缓存函数240* 240* 2 = 115200

讲真,这个大小用串口发送数据。的话要1/8秒才能完成。换句话说。就是8帧的刷新率。就这刷新率。也最多能当个钟用了

就是最基本的打点函数。

有关SD卡的读写操作的技巧可以看我的BADAPPLE

里面有我关于图片数组的见解。我在这里简单重复一下。现在需要准备一个三维数组。分别是长度、宽度和深度(颜色)。在c语言中。如果要定义对数组处理的函数。就需要弄清楚指针和数组的关系。

以二位数组为例:如果要处理char类型二位数组。可以定义一个指向char的指针。然后将二位数组得到首地址赋给指针。然后对指针进行操作。这样做可以,但是不严谨,而且繁琐:首先二位数组的首地址是指向数组的指针,而不是指向char的指针。直接赋值并不严谨。其次,需要知道数组和指针的增量关系然后构筑算法来解决这个问题。繁琐。

也可以使用二重指针。定义一个固定深度的指针。指向数组指针。到时候取用的时候只需要按照数组的表达方式就可以取到对应的值。这种方法的缺点是。你必须知道你要处理的指针的深度。

image-20210118125430743

还有一些别的赋值方法。这里就不介绍了。参照上面的图片看吧。

在函数的多维数组中,第一括号知识表明这是一个指针,里面的内容可以省略外,后面的括号里的内容都不能省略。

由于一副图片太大,以至于stm32没有足够的RAM来存放这张图片,所以需要直接在内存卡上直接处理数组

相信大叫都知道文件这种东西。但是要仔细说明文件的本质,这还是有点难度的。所以在这之前,先要明白文件是什么。

image-20210118144410814

SD卡的读取思路

我是这样想的,以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屏不闪是不可能的了所以我要做的,就是尽量让他不要闪

关于局部刷新

由于我使用的是局部刷新。如果刷新速度太快。会导致画面闪烁。而如果刷新太慢又会导致有小点残留在画面当中。这是我不能仍受的。所以我打算把那一块用矩形填起来。

要按照时间的规律去刷新的话。也能节省一部分资源。不能按照时间刷新了看来。只能按照坐标刷新。对应的分针在哪个块就刷新哪快。

关于时钟延迟初始化

经过群友提醒,我发现这个板子由于是旁路时钟。所以会出现上电瞬间。时钟初始化失败。

image-20210119195607130

直接将HSE的timout改大一点就行

c60743b467ad4cd90879937fdfd08df

这里的数值改成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里面能解决的问题。都只能放在中断里面了。资源消耗太多。

完事收工

完成功能介绍:

  1. 时间显示功能。能显示单片机中记录的时间。其时间可以用上位机的特定的指令修改。格式如下

    "time:y%M%md%dYh%Hm%Ms%S"
    

    可以显示时间可以精确到秒。支持多次修改。

  2. 整点报时功能。具体就是到了几点就会响几下。可以通过按键来取消报点功能。报点功能是否有效。屏幕左上角会显示。

  3. 根据重力调整时钟位置。注:由于数字是由字库显示的。所以数字和字符的朝向不能改变。

    整个时钟会随着重力的改变而改变方向。

  4. 显示考研剩余天数。因为我是2022考研狗。所以需要时刻显示天数提醒我自己。由于天数计算的函数没有设计完全。题目中也没有要求。所以就不显示了。

  5. SD卡图片显示函数。(需要提前将数组转化存入SD卡)不演示

  6. 按键对应不同的功能:

    1. 控制报时是否有效

    2. 关闭背光

    3. 开启背光

    4. 未定义

      注:按键功能定义不能太长。程序可能会跑飞。

由于要保证屏幕的刷新率和反应速度,很多地方我都进行了简化处理。如果想加入更多的功能。比如卡尔曼滤波。可以考虑减小屏幕的刷新率。

所用板子资源介绍:

  1. 定时器2、3、4

  2. LCD——SPI协议

  3. 按键——外部中断

  4. MPU6050——I2C协议
    可以显示时间可以精确到秒。支持多次修改。

  5. 整点报时功能。具体就是到了几点就会响几下。可以通过按键来取消报点功能。报点功能是否有效。屏幕左上角会显示。

  6. 根据重力调整时钟位置。注:由于数字是由字库显示的。所以数字和字符的朝向不能改变。

    整个时钟会随着重力的改变而改变方向。

  7. 显示考研剩余天数。因为我是2022考研狗。所以需要时刻显示天数提醒我自己。由于天数计算的函数没有设计完全。题目中也没有要求。所以就不显示了。

  8. SD卡图片显示函数。(需要提前将数组转化存入SD卡)不演示

  9. 按键对应不同的功能:

    1. 控制报时是否有效

    2. 关闭背光

    3. 开启背光

    4. 未定义

      注:按键功能定义不能太长。程序可能会跑飞。

由于要保证屏幕的刷新率和反应速度,很多地方我都进行了简化处理。如果想加入更多的功能。比如卡尔曼滤波。可以考虑减小屏幕的刷新率。

所用板子资源介绍:

  1. 定时器2、3、4
  2. LCD——SPI协议
  3. 按键——外部中断
  4. MPU6050——I2C协议
  5. SD卡——硬件SPI及FATFS
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值