IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32CUBEMX 配置12脚3641BS以及串口显示RTC时间 -> 正文阅读

[嵌入式]STM32CUBEMX 配置12脚3641BS以及串口显示RTC时间

最近在学习STM32F4系列的RTC时钟系统,同时也在学习4位7段数码管显示驱动。而市面上很多3641BS数码管都是集成了74HC595移位寄存器芯片的PCB板,故网上大多基于STM32驱动该型号数码管的驱动都是基于五引脚(VCC, DIO, RCLK, SCLK, GND)设计的。但我手头只有最原始的12引脚版本,因此在这篇博客里我会讲解最原始版本的,也当作是一个备忘录和对RTC时钟唤醒中断配置以及3641BS工作原理的认识吧。

1. 实验原理

如下图,四位七段数码管有12个引脚。除了显示"abcdefg"的7段和显示小数点的"h"之外,还有4个引脚D1、D2、D3和D4用作"位"引脚。当四个数码管的"位"引脚为高电平时,相应的数码管被点亮。
四个数码管的显示原理是连续扫描D1、D2、D3、D4,然后相应的八段管依次亮起。由于点亮速度很快,小于人眼分辨的时间差值极限,所以看起来就像四个数码管同时显示。

对于STM32实时时钟(RTC)来说,它是一个独立的BCD定时器/计数器,提供日历时钟、两个可编程报警中断和一个具有中断功能的可编程定期唤醒标志,还包含一个自动唤醒单元来管理低功耗模式。
两个32位的寄存器包含了秒、分、小时(12或24小时格式)、星期、日期、月份和年份的二进制十进制(BCD)格式。此外,亚秒值以二进制格式提供。
系统可以自动将每月的天数偏移到28、29(闰年)、30和31天。还可提供夏令时补偿。其他32位寄存器包含可编程的报警亚秒、秒、分钟、小时、星期和日期。此外,晶体精度的偏差可以通过数字校准功能进行补偿。在上电复位后,所有的RTC寄存器都受到保护,防止可能的异常写入。无论设备状态如何(工作模式、低功耗模式或重启),只要电源电压保持在工作范围内,RTC就不会停止工作。

?

2.实验材料


STM32F407ZG开发板一块
面包板及配套连接线(公对母)
四位共阳极数码管3641BS(最原始版本,不包含任何集成芯片)
4个220Ω限流电阻

3.实验电路连接图

?

数码管的a~h引脚分别接到单片机的PC0~PC7引脚,d0,d1,d2,d3引脚分别接到单片机的PF0~PF3引脚,并且在中间串联220Ω的限流电阻,以免永久损坏数码管LED。

4. CUBEMX配置以及代码编写

首先将A~H八端初始化为高电平,D0~D3初始化为低电平,每个引脚上下拉模式为浮空

?RCC系统时钟源的HSE, LSE都调为晶振源

?

打开RTC时钟源和日历,启用内部时钟唤醒,把时间调为24小时制,存储模式用二进制(这点很重要!!!等会读取时间的时候也要以二进制形式读取,否则数字会有偏差),设置好时分秒以及年月日、星期几,根据自己需要是否关闭夏令时,唤醒定时器的时钟输入设置为1Hz(即RTC_WAKEUPCLOCK_RTCCLK / 16),其他参数默认就好

?

?接下来配置唤醒中断以及优先级。在这里,我将中断优先级分组设置为2位抢占优先级、2位响应优先级,同时将唤醒中断激活并将抢占、响应优先级均设置为2,以保证不会干扰其他系统底层中断的运行。最后勾选生成中断请求处理函数和HAL调用处理函数。

?

为了在中断处理函数中通过printf输出日期和其他更详细的信息,需要启用串口。在SYS中将Debug模式调为Serial Wire,并且启用USART1,设置为异步传输模式,波特率115200,其他默认就好。

确认无误后生成基于HAL库的KEIL5 MDK工程文件

接下来到了实现关键代码部分的时候了!?

首先,为了保证RTC断电后时间不丢失、同时每次上电后时间日期不被重复设置,需要在RTC 后备寄存器中留下标记值,以标记是否为第一次设置时间。但后备寄存器需要启用写权限,于是在main()函数HAL_Init()初始化完成后插入:

HAL_PWR_EnableBkUpAccess();

然后在rtc.c文件中的MX_RTC_Init()函数初始化时间部分前后加上判断后备寄存器是否有标记的逻辑,若没有标记则初始化并写寄存器。

/* USER CODE BEGIN Check_RTC_BKUP */
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0x5050) { // Add this line
  /* USER CODE END Check_RTC_BKUP */
  sTime.Hours = 14;
  sTime.Minutes = 10;
  sTime.Seconds = 12;
  // ....

  /* USER CODE BEGIN RTC_Init 2 */
	HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x5050); // Add this line
} // Add this line
  /* USER CODE END RTC_Init 2 */
  /** Enable the WakeUp
  */
  if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0, RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)
  {
    Error_Handler();
  }

添加中断回调处理函数

int finale = 0;

void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) {
	char buf[40];
	RTC_TimeTypeDef RTC_TimeStruct;
  RTC_DateTypeDef RTC_DateStruct;
	
	HAL_RTC_GetTime(hrtc,&RTC_TimeStruct,RTC_FORMAT_BIN);
	sprintf((char*)buf,"Time: %02d:%02d:%02d",RTC_TimeStruct.Hours,RTC_TimeStruct.Minutes,RTC_TimeStruct.Seconds);
	printf("%s\r\n",buf);
	
	HAL_RTC_GetDate(hrtc,&RTC_DateStruct,RTC_FORMAT_BIN);
	sprintf((char*)buf,"Date: 20%02d-%02d-%02d  Day %d",RTC_DateStruct.Year,RTC_DateStruct.Month,RTC_DateStruct.Date,RTC_DateStruct.WeekDay);
	printf("%s\r\n",buf);
	
	finale = RTC_TimeStruct.Hours * 100 + RTC_TimeStruct.Minutes;
}

并且在头文件中添加显示小时和分钟的外部int值,供主函数while循环中扫描显示在数码管上

extern int finale;

最后在usart.c和usart.h中添加fputc重载函数,以保证将printf函数输出设备从控制台重定向到串口1

#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart1,(uint8_t*)&ch, 1, 0xFFFF);
  return ch;
}

可能有同学就在这里问了:为什么我加了printf函数以后的程序编译烧写进板子,其系统不能启动,出现死机的情况?这是因为在嵌入式的编程中HAL库并没有对IO函数的底层实现(比如_sys_exit(), _sys_open(), _sys_close()这些函数),使得设备运行时会进入软件中断BAEB处,即进入了半主机模式,这时就需要__use_no_semihosting_swi这个声明,同时用空壳实现这些函数(dummy函数),使程序遇到这些文件操作函数时不停在此中断处。将以下代码粘贴进新建的源文件并加入工程即可:

/* USER CODE BEGIN 0 */
#include <time.h>

#pragma import(__use_no_semihosting_swi)  
#pragma import(_main_redirection)

const char __stdin_name[150];  
const char __stdout_name[150];  
const char __stderr_name[150];  
typedef int FILEHANDLE;  
 
void _sys_exit(int status)  
{  
    while(1);  
}  

FILEHANDLE _sys_open(const char *name, int openmode)  
{  
    return 0;  
}  

int _sys_close(FILEHANDLE fh)  
{  
    return 0;  
}  

int _sys_write(FILEHANDLE fh, const unsigned char *buf, unsigned len, int mode)  
{  
    return 0;  
}  

int _sys_read(FILEHANDLE fh, unsigned char*buf, unsigned len, int mode)  
{  
    return 0;  
}  

int _sys_istty(FILEHANDLE fh)  
{  
    return 0;  
}  

int _sys_seek(FILEHANDLE fh, long pos)  
{  
    return 0;  
}  

int _sys_ensure(FILEHANDLE fh)  
{  
    return 0;  
}  

long _sys_flen(FILEHANDLE fh)  
{  
    return 0;  
}  

int _sys_tmpnam(char *name, int fileno, unsigned maxlength)  
{  
    return 0;  
}  

void _ttywrch(int ch)  
{  
}

time_t time(time_t *t)  
{  
    return 0;  
}  
int remove(const char *filename)  
{  
    return 0;  
}  

char *_sys_command_string(char *cmd, int len)  
{  
    return 0;  
}  

clock_t clock(void)  
{  
    return 0;  
}

/* USER CODE END 0 */

接下来的重中之重,就是实现四位七段数码管的驱动程序了。首先打出码表和对应的引脚数组

int dat[10][8] = {
    // Last bit is reserved for H (i.e. point)
    {0, 0, 0, 0, 0, 0, 1, 1}, //0
    {1, 0, 0, 1, 1, 1, 1, 1}, //1
    {0, 0, 1, 0, 0, 1, 0, 1}, //2
    {0, 0, 0, 0, 1, 1, 0, 1}, //3
    {1, 0, 0, 1, 1, 0, 0, 1}, //4
    {0, 1, 0, 0, 1, 0, 0, 1}, //5
    {0, 1, 0, 0, 0, 0, 0, 1}, //6
    {0, 0, 0, 1, 1, 1, 1, 1}, //7
    {0, 0, 0, 0, 0, 0, 0, 1}, //8
    {0, 0, 0, 0, 1, 0, 0, 1} //9
};

uint16_t dbit[4] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3}; //使能位引脚(D0~D3), 高电平有效
uint16_t tbit[8] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3, GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7}; //控制点亮哪根数码管(a~h), 低电平有效

然后是显示单个数字

void DisplayInt(int n, int nb, int punto) {
    int i = 0;
    if(n < 10) {
        for(i = 0; i < 8; i++) {
            HAL_GPIO_WritePin(GPIOC, tbit[i], 
					(nb == 1 && i == 7) ? (punto ? 0 : 1) : dat[n][i]);
        }
    }
}

接下来是四位数字同时显示,punto形参表示是否显示小时和分钟之间的小数点

void delay_us(uint32_t nus) {
    uint32_t ticks;
    uint32_t told,tnow,tcnt=0;
    uint32_t reload=SysTick->LOAD;
	uint32_t freq = HAL_RCC_GetSysClockFreq();
    ticks=nus * freq / 1e6;
    told=SysTick->VAL;
    while(1) {
        tnow=SysTick->VAL;
        if(tnow!=told) {
            if(tnow<told)tcnt+=told-tnow;
            else tcnt+=reload-tnow+told;
            told=tnow;
            if(tcnt>=ticks) break;
        }
    }
}

void DisplayNumber(int x, int punto) {
    int nb = 0;
    int display_arr[5] = {0};
    int bit_base = 1000;
    for(nb = 0; nb < 4; nb++) {
        if(x / bit_base != 0) {
            display_arr[nb] = x/bit_base;
            x = x%bit_base;
        } else {
            display_arr[nb] = 0;
        }
        bit_base = bit_base / 10;
    }
    for(nb = 0; nb < 4; nb++) {
        DisplayInt(display_arr[nb], nb, punto);
        HAL_GPIO_WritePin(GPIOF, dbit[nb], GPIO_PIN_SET); // 4位数字轮流显示200us后熄灭,形成循环,小于人眼分辨时间,看上去就是连续的
        delay_us(200);
        HAL_GPIO_WritePin(GPIOF, dbit[nb], GPIO_PIN_RESET);
    }
}

最后是显示小时和分中的总函数,要保持一直显示而不熄灭,必须将DisplayNumber()置入该函数的定时循环之中,单位是毫秒,以下是代码:

void DisHHMM(int x, uint32_t ms, int punto) {
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = ms;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while((HAL_GetTick() - tickstart) < wait)
  {
		DisplayNumber(x, punto);
  }
}

至此,基础函数部分已经全部实现完毕,可以在main()函数中测试了。

int main(void)
{
  // ...
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_RTC_Init();
  
  // ...

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		if(finale != 0) {
			DisHHMM(finale, 500, 1);
			DisHHMM(finale, 500, 0);
		}
  }
  /* USER CODE END 3 */
}

5. 实验结果

?

6. 参考文章?

1.?stm32半主机模式 - chenguan - 博客园

2.?How to drive a 7 segment display directly on Raspberry Pi in Python – RasPi.TV

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-01-28 12:04:46  更:2022-01-28 12:06:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 10:36:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码