你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【NUCLEO-U083RC评测】⑨电子墨水屏RTC低功耗日历Demo

[复制链接]
小萝卜啦啦啦 发布时间:2024-6-8 22:46
本文是STM32U0测评系列的最后一篇。我认为STM32U0最吸引我的就是他极致的低功耗,ST官方也说他非常很适用于水表等产品的应用,于是我只做了一个电子墨水屏RTC日历。一开始我是想做日历+时钟+温湿度。但是很可惜我手上的这块电子墨水屏不能局部刷新,只能全刷,而全刷要15s,也就是说1分钟内只有45s是正常显示的,这也太鸡肋了,完全没有实用性可言,所以最终决定就只做日历,一天刷新一次,功耗也是嘎嘎低由于中途有很多调试工作,我就不再一步一步讲解是怎么做的,简单讲解一下demo功能、接线、代码逻辑、上位机使用注意、后续改进想法等一、简述功能使用STM32U08 NUCLEO开发板外接一块我自己做的电子墨水屏驱动板(软硬件已开源,地址:https://github.com/BUYITAO/My_E-Paper_Driver),显示当前的日期,每天24点整由RTC闹钟唤醒刷新屏幕,平时MCU都处于STANDBY,这样可以让整个系统处于最低功耗状态。正常显示如下(偷懒了,没有做界面美化设计,请见谅
4.png
系统首次上电后由于RTC参数丢失,会处于默认的预设状态,需要用户手动设置一下时间,此时电子墨水屏显示“Please config RTC data”,如下图所示
3.png
用户可以通过串口发送配置参数(这个我做了一个上位机可以一键发送配置指令),MCU收到后,刷新屏幕,再进入standby。上位机界面如下图
5.png
二、硬件连线
1.png
墨水屏驱动板     STM32
MOSI          PA7
CS            PC9
RST           PC8
CLK           PA1
DC            PC6
BUSY           PC5
照片不太看得清楚开发板上的接线,我把原理图放上来
2.png
然后串口部分我借用了STlink的VCP(这个最后改进点部分我会讲到,用这个其实对功耗影响挺大的,后续打算改掉)
三、代码简单讲解
根据之前做的功能设计,我在上一篇“【NUCLEO-U083RC评测】⑧RTC&STANDBY Alarm唤醒功能”工程的基础上继续开发。
CUBEMX打开SPI,然后配置了GPIO。这样就可以实现与电子墨水屏的通讯
6.png
7.png
然后把GPIO和SPI的库改成LL库(个人习惯,这些基础的外设以前LL库用惯了,毕竟可以看代码学习对应寄存器的操作),然后生成代码即可
8.png
***这里需要注意一下生成的代码有两个BUG,毕竟现在U0的PACK才第一版,有点BUG很正常,希望ST可以看到我的这个文章,在后续的版本中修复一下
第一个是在main.h中,红框这个没有换行,会导致编译报错
9.png
第二个是生成的工程会强制设置为V6编译器,V6编译会疯狂报错,改成V5就好
10.png
然后关于电子墨水屏的移植、配置啥的我就不细讲了,在我分享的github上有详细的readme。接下来简单看一下功能代码
while1前我会初始化墨水屏,然后判断是否为首次上电。如果是首次上电,就让墨水屏显示“Please config RTC data”,反之就清除标志位,刷新屏幕(到这里就是RTC闹钟到时间了,要刷新屏幕,显示新的日期),然后再进入Standby
  1.   /* 初始化墨水屏 */
  2.   E2213JS0C1_Init(0);
  3.   /* 是否为首次上电 */
  4.   if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) == RESET)
  5.   {
  6.     printf("normal run\r\n");
  7.     /* 打印RTC时间 */
  8.     print_rtc_data();
  9.     E_Paper_show_first_power_on_page();
  10.   }
  11.   /* 从standby唤醒后的复位 */
  12.   else
  13.   {
  14.     printf("wkup from standby run\r\n");
  15.     /* 清除standby的标志位 */
  16.     __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
  17.     /* 清除闹钟标志位 */
  18.     __HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF);
  19.     /* 刷新屏幕 */
  20.     E_Paper_show_calendar();
  21.     /* 进入standby模式 */
  22.     HAL_PWR_EnterSTANDBYMode();
  23.   }
复制代码
11.png
如果是首次上电的,可以看到没有调用standby函数,所以他可以向下进入while1,while1中的函数如下
  1.     if(HAL_UART_GetState(&hcom_uart[COM1]) == HAL_UART_STATE_READY)
  2.     {
  3.       if(HAL_UART_Receive(&hcom_uart[COM1], (uint8_t*)&buffer[idx], 1, HAL_MAX_DELAY) == HAL_OK)
  4.       {
  5.         if(buffer[idx] == '\n') // 假设\r\n'作为结束标志
  6.         {
  7.           buffer[idx] = 0; // 添加字符串结束符
  8.           idx = 0; // 重置缓冲区索引
  9.           if(ParseATCommandAndSetRTC(buffer))
  10.           {
  11.             printf("Parse AT Command success\r\n");
  12.             /* 刷新屏幕 */
  13.             E_Paper_show_calendar();
  14.             printf("enter standby\r\n");
  15.             /* 进入standby模式 */
  16.             HAL_PWR_EnterSTANDBYMode();
  17.           }
  18.           else
  19.           {
  20.             printf("Parse AT Command fail\r\n");
  21.           }
  22.         }
  23.         else
  24.         {
  25.           idx++; // 缓冲区索引增加
  26.           if(idx >= sizeof(buffer) - 1) idx = 0; // 防止缓冲区溢出
  27.         }
  28.       }
  29.     }
复制代码
12.png
会一直去获取串口收到的数据,如果找到\n就认为收到一包数据,然后去解析,解析成功就刷新屏幕显示日期,然后进去standby。反之继续接收
下面看看解析函数
  1. bool ParseATCommandAndSetRTC(char* buffer)
  2. {
  3.   RTC_TimeTypeDef sTime = {0};
  4.   RTC_DateTypeDef sDate = {0};
  5.   unsigned int temp_year;
  6.   unsigned int temp_month;
  7.   unsigned int temp_day;
  8.   unsigned int temp_weekDay;
  9.   unsigned int temp_hour;
  10.   unsigned int temp_min;
  11.   unsigned int temp_sec;
  12.   /* 检查命令的头对不对 */
  13.   if(strncmp(buffer, "AT+configRTCdata=", 17) != 0) return false;
  14.   /* 跳过命令头部分,再跳过年的前2个数字 */
  15.   char* dataPtr = buffer + 17 + 2;
  16.   /* 解析年、月、日... */
  17.   if(sscanf(dataPtr, "%2u-%2u-%2u-%2u-%2u:%2u:%2u",
  18.       &temp_year,
  19.       &temp_month,
  20.       &temp_day,
  21.       &temp_weekDay,
  22.       &temp_hour,
  23.       &temp_min,
  24.       &temp_sec) != 7)
  25.   {
  26.       return false;
  27.   }
  28.   sDate.Year = uint_to_bcd(temp_year);
  29.   switch (temp_month)
  30.   {
  31.     case 1: sDate.Month = RTC_MONTH_JANUARY; break;
  32.     case 2: sDate.Month = RTC_MONTH_FEBRUARY; break;
  33.     case 3: sDate.Month = RTC_MONTH_MARCH; break;
  34.     case 4: sDate.Month = RTC_MONTH_APRIL; break;
  35.     case 5: sDate.Month = RTC_MONTH_MAY; break;
  36.     case 6: sDate.Month = RTC_MONTH_JUNE; break;
  37.     case 7: sDate.Month = RTC_MONTH_JULY; break;
  38.     case 8: sDate.Month = RTC_MONTH_AUGUST; break;
  39.     case 9: sDate.Month = RTC_MONTH_SEPTEMBER; break;
  40.     case 10: sDate.Month = RTC_MONTH_OCTOBER; break;
  41.     case 11: sDate.Month = RTC_MONTH_NOVEMBER; break;
  42.     case 12: sDate.Month = RTC_MONTH_DECEMBER; break;
  43.     default: return false;
  44.   }
  45.   sDate.Date = uint_to_bcd(temp_day);
  46.   switch (temp_weekDay)
  47.   {
  48.     case 1: sDate.WeekDay = RTC_WEEKDAY_MONDAY; break;
  49.     case 2: sDate.WeekDay = RTC_WEEKDAY_TUESDAY; break;
  50.     case 3: sDate.WeekDay = RTC_WEEKDAY_WEDNESDAY; break;
  51.     case 4: sDate.WeekDay = RTC_WEEKDAY_THURSDAY; break;
  52.     case 5: sDate.WeekDay = RTC_WEEKDAY_FRIDAY; break;
  53.     case 6: sDate.WeekDay = RTC_WEEKDAY_SATURDAY; break;
  54.     case 7: sDate.WeekDay = RTC_WEEKDAY_SUNDAY; break;
  55.     default: return false;
  56.   }
  57.   sTime.Hours = uint_to_bcd(temp_hour);
  58.   sTime.Minutes = uint_to_bcd(temp_min);
  59.   sTime.Seconds = uint_to_bcd(temp_sec);
  60.   sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  61.   sTime.StoreOperation = RTC_STOREOPERATION_RESET;
  62.   set_rtc_data(sTime, sDate);
  63.   print_rtc_data();
  64.   return true;
  65. }
复制代码
如果解析成功就设置RTC,反之返回false。
我输入的串口数据是十进制的,但是RTC使用的是BCD,所以需要转换一下,这就是uint_to_bcd函数,他的内容如下
  1. uint8_t uint_to_bcd(unsigned int num)
  2. {
  3.   // 检查输入范围,确保0-99之间
  4.   if (num > 99)
  5.   {
  6.     // printf("Error: Input out of range. Maximum 99 allowed.\n");
  7.     return 0;
  8.   }
  9.   // 将十进制数转换为BCD
  10.   uint8_t bcd = (num / 10) << 4 | (num % 10);
  11.   return bcd;
  12. }
复制代码
核心的逻辑代码都在这里了,屏幕显示的函数与具体逻辑无关,我就不展示了,可以自己去看附件中代码
四、上位机注意事项
上位机软件是用python+tkinter+serial写的,所以保证你的电脑上安装了python环境(我这边是最新的3.12.4),然后要安装tkinter、serial的库,否则运行时会报错:“ModuleNotFoundError: No module named ‘tkinter'”或ModuleNotFoundError: No module named ‘serial'”(我忘记是serial还是pyserial,大致就是这么个意思
你可以用以下指令查看是否安装这两个库
  1. python -m tkinter
复制代码
如果安装了,会弹出一个窗口,这是tkinter的一个demo
  1. python -m serial
复制代码
如果安装了,会打印 出可以用的COM口
如果你没安装,可以用以下命令安装serial
  1. pip install pyserial
复制代码
tkinter一般都是在安装python时自动安装的,如果你没有可以看一下这个文章
然后上位机我也提供源码在附件,如果EXE实在无法运行,可以尝试用VSCODE调试运行,我这边之前就是调试可以运行,EXE无法运行,需要安装东西,很奇怪
然后便于测试,我提供了强制写入23点59分30秒的版本,可以通过注释以下代码来实现
13.png
五、后续改进想法
这个demo现在其实处于一个初始状态,低功耗部分还没有调试过,目前已知的硬件上肯定需要做调整,否则功耗挺高的
1.LED3这个灯要干掉,他一直亮着耗电
2.供电及串口,之前我测试standby功耗是是选择使用CHG跳帽的,现在为了可以借用STLINK的VCP被迫选择STLK,这会增大功耗,后续我打算使用外部的串口,供电改回CHG。板子上需要把SB48/45焊接一下,这样串口可以引到arduino的D1、D0口上,配置完时钟后拔掉接线
14.png
3.STlink VCP的R23、24电阻干掉,我一直怀疑MCU会有电从这个漏过去,导致之前测量的功耗高,反正现在打算用外挂的串口,这个我就直接干掉。
4.配置RTC的机制有点不灵活,用户想要配置RTC,必须断电再上电,不友好。毕竟RTC时间长了就会偏,手动重校时是必须的。后续可以把板载的用户按键利用起来,把它作为唤醒源,当按下后,MCU WKUP,等待用于输入配置指令。多久没收到正确的指令就再进入standby
5.美化一下界面
6.增加wifi模块,这样可以实现每天网络校时,并且还可以获取天气预报信息,让屏幕看上去不那么空
7.如果有合适的液晶段码屏,可以加上,用于显示温湿度数据,躲开了电子墨水屏的问题
感谢各位读到这里,如果你有更好的想法或者有疑问或者代码有错误,欢迎在评论区交流
项目源码及上位机如下
STM32U0_RTC_E-Paper_calendar.rar (13.95 MB, 下载次数: 3)
收藏 评论1 发布时间:2024-6-8 22:46

举报

1个回答
STMCU-管管 回答时间:2025-4-30 09:40:05

详细👍

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版