【STM32标准库】【自制库】8位8段数码管(74HC595)【软件部分】


文章基于适用于STM32F4系列,作者使用STM32F401CCU6开发板。
本文章基于此系列和开发板展开讨论。

硬件部分

模块的介绍和硬件原理在这里
【基础知识】【模块介绍】8位8段数码管(74HC595)【硬件部分】

软件设计

需求分析

  • 8位“同时”显示
  • 数码管可以显示0-F的数值和横线作为分隔符
  • 可以设置小数点位置
  • 能动态修改要显示的值
  • 显示过程中可以进行其他运算

编程思路

总体上使用硬件部分提到过的扫描,间隔一段时间分别点亮8位数码管,使用定时器中断产生一定的时间来减少CPU的占用。

  1. 字码数据编写
  2. 初始化(GPIO,NVIC,定时器)
  3. 数据发送程序
  4. 定时扫描程序

字码数据和宏定义

这是定义在头文件中,为了便于使用
这里使用了固件库的头文件和上次的延迟函数文件

TUBE.H

#ifndef _TUBE_H
#define _TUBE_H

#include "stm32f4xx.h"                  				// 注意更改这个文件的#define STM32F401xx为自己的芯片型号
#include "Delay.h"										//自制的延迟文件

//更改端口时需要更改(是关于GPIO配置所需要的东西)
#define Tube_SCLK			GPIO_Pin_0
#define Tube_RCLK			GPIO_Pin_1
#define Tube_DIO			GPIO_Pin_2
#define Tube_RCC			RCC_AHB1ENR_GPIOAEN
#define Tube_GPIOx			GPIOA

//更改定时器时需要更改(关于定时器配置所需要的东西)
#define	Tube_Tim_RCC_Cmd	{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);}	//这里因为不确定所使用的定时器,因此直接将打开时钟函数编写成宏定义
#define	Tube_Tim			TIM3
#define	Tube_Tim_IRQChannel	TIM3_IRQn			
#define Tube_Priority_1		3							//主中断优先级设置
#define Tube_Priority_2		3							//副中断优先级

//数码管的值 按照共阳接法的字码值
typedef enum
{
	Tube_0		=	0xC0,
	Tube_1		=	0xF9,
	Tube_2		=	0xA4,
	Tube_3		=	0xB0,
	Tube_4		=	0x99,
	Tube_5		=	0x92,
	Tube_6		=	0x82,
	Tube_7		=	0xF8,
	Tube_8		=	0x80,
	Tube_9		=	0x90,
	Tube_A		=	0x88,
	Tube_b		=	0x83,
	Tube_C		=	0xC6,
	Tube_d		=	0xA1,
	Tube_E		=	0x86,
	Tube_F		=	0x8E,
	Tube_Line	=	0xbf,						//显示-
	Tube_Bleak	=	0xff,						//全不显示
	Tube_Spot	=	0x7F,						//显示小数点
	Tube_White	=	0x00						//全都显示
}Tube_Val_Typedef;

//这里的几个变(常)量定义在 TUBE.C 文件中,因此使用extern关键字
extern Tube_Val_Typedef Tube_Val[8];			//这个是存的
extern const Tube_Val_Typedef Tube_Lest[16];	//这个是0-F的字符,使用时直接按数组的方式调用
extern uint8_t Tube_Spot_P;						//小数点位置,从左向右数的位数

//函数定义,这下面的就是所使用的全部函数了
void Time_Init(void);
void Tube_Out_Send(uint8_t X);
void Tube_Show(void);
void Tube_Out(uint8_t Digit,Tube_Val_Typedef Data,FunctionalState Spot);
void Time_Init(void);
void Tube_Clear(void);
void Tube_Init(void);
void Tube_Send_8Bit(Tube_Val_Typedef *Data,uint8_t Spot_P);
void Tube_Send_Scan(Tube_Val_Typedef *Data,uint8_t Spot_P);


//更改定时器时需要更改,如出现重复定义请删除另一个
//重定义可能是你已经使用过这个定时器的中断服务了,也可能是固件库预先写在一个文件中了,注意辨别即可
void TIM3_IRQHandler(void);

#endif

TUBE.c

//8个数码管的值 从左到右
Tube_Val_Typedef Tube_Val[8]={Tube_White,Tube_White,Tube_White,Tube_White,Tube_White,Tube_White,Tube_White,Tube_White};

//数值列表分别是0-f
const Tube_Val_Typedef Tube_Lest[16]={Tube_0,Tube_1,Tube_2,Tube_3,Tube_4,Tube_5,Tube_6,Tube_7,Tube_8,Tube_9,Tube_A,Tube_b,Tube_C,Tube_d,Tube_E,Tube_F};

//小数点的位置 从左到右数
uint8_t Tube_Spot_P=0xff;

定义时使用的初值

初始化

GPIONVIC定时器中断的配置之前介绍过了,点击查看详情。

定时器中断,NVIC初始化配置

//定时器和中断初始化
void Time_Init(void)
{
	
	TIM_TimeBaseInitTypeDef TIM_Init_TypeDef;
	NVIC_InitTypeDef NVIC_Init_TypeDef;
	Tube_Tim_RCC_Cmd											//打开时钟,这是个宏定义,在头文件中
	TIM_Init_TypeDef.TIM_ClockDivision=TIM_CKD_DIV1;			//滤波器不分频
	TIM_Init_TypeDef.TIM_CounterMode=TIM_CounterMode_Up;		//向上计数模式

	TIM_Init_TypeDef.TIM_Period=840 - 1;						//预分频
	TIM_Init_TypeDef.TIM_Prescaler=250 - 1;						//周期
	/*2.5ms*/
	
	TIM_Init_TypeDef.TIM_RepetitionCounter=0;					//通用定时器设0就行

	TIM_TimeBaseInit(Tube_Tim,&TIM_Init_TypeDef);				//初始化
	TIM_ITConfig(Tube_Tim,TIM_IT_Update,ENABLE);				//开定时器溢出中断
	
	NVIC_Init_TypeDef.NVIC_IRQChannel=Tube_Tim_IRQChannel;		//宏定义,看头文件
	NVIC_Init_TypeDef.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init_TypeDef.NVIC_IRQChannelPreemptionPriority=Tube_Priority_1;
	NVIC_Init_TypeDef.NVIC_IRQChannelSubPriority=Tube_Priority_2;
	NVIC_Init(&NVIC_Init_TypeDef);								
	TIM_Cmd(Tube_Tim,ENABLE);									//别忘打开定时器
}

GPIO初始化和整个工程的初始化,使用时直接调用此函数即可

//初始化
void Tube_Init(void)
{
	//GPIO初始化
	GPIO_InitTypeDef GPIO_InitTypeDef;
	RCC_AHB1PeriphClockCmd(Tube_RCC,ENABLE);
	GPIO_InitTypeDef.GPIO_Mode=GPIO_Mode_OUT;					//输出模式
	GPIO_InitTypeDef.GPIO_OType=GPIO_OType_OD;					//开漏输出,安全一些
	GPIO_InitTypeDef.GPIO_Pin=Tube_SCLK|Tube_RCLK|Tube_DIO;
	GPIO_InitTypeDef.GPIO_PuPd=GPIO_PuPd_UP;					//上拉
	GPIO_InitTypeDef.GPIO_Speed=GPIO_Fast_Speed;
	GPIO_Init(Tube_GPIOx,&GPIO_InitTypeDef);
	GPIO_ResetBits(Tube_GPIOx,Tube_SCLK|Tube_RCLK|Tube_DIO);	//初始设为0
	
	//NVIC组设定
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//在调用NVIC初始化前设置
	
	//定时器,NVIC初始化
	Time_Init();
	
	//全部点亮0.5s后关闭  默认值是全部点亮,500ms后设为黑就行了
	Delay_ms(500);
	Tube_Clear();					//就是改Tube_Val数组的数值
}

数据发送

在硬件部分中说过,模块内有2个595芯片分别存入数据和片选
根据硬件部分中的介绍可以知道

先发送字码,后发送片选码,按照从高到低的顺序发送,在SCLK的上升沿送入数据
片选码的顺序的从高到低对应的是模块上的从左到右
RCLK的上升沿将显示

发送数据

//发送数据
void Tube_Out_Send(uint8_t X)
{
	uint8_t i;
	//从高到低发送数据
	for(i=8;i>=1;i--)
	{	
		if (X&0x80)
		{
			GPIO_WriteBit(Tube_GPIOx,Tube_DIO,Bit_SET);
		}
		else
		{
			GPIO_WriteBit(Tube_GPIOx,Tube_DIO,Bit_RESET);
		}
		X<<=1;
		//必须加延迟
		Delay_us(2);
		//在SCLK中产生上升沿 表示一位数据送入
		GPIO_WriteBit(Tube_GPIOx,Tube_SCLK,Bit_RESET);
		Delay_us(2);
		GPIO_WriteBit(Tube_GPIOx,Tube_SCLK,Bit_SET);
		Delay_us(2);
	}
}

注意:需要加上延迟,STM32的GPIO翻转速度超过了595的极限,推荐2us

显示

//显示
void Tube_Show(void)
{
	//在RCLK产生上升沿 锁存显示
	GPIO_WriteBit(Tube_GPIOx,Tube_RCLK,Bit_RESET);
	GPIO_WriteBit(Tube_GPIOx,Tube_RCLK,Bit_SET);
}

发送一位8段数码管的数据

//发送一位数码管的数据 Digit:从左向右数的数据位数 Data:数据 Spot:是否显示小数点
void Tube_Out(uint8_t Digit,Tube_Val_Typedef Data,FunctionalState Spot)
{
	//先发送要显示的值
	if(Spot==ENABLE)
	{
		Tube_Out_Send(0x7F&Data);		//设置小数点
	}
	else
	{
		Tube_Out_Send(Data);
	}

	//后发送显示的位置
	Tube_Out_Send(0x80>>Digit);
	Tube_Show();
}

扫描

发送8位的数据(一种扫描方式,实验时用的,每个数码管的点亮时间很短,导致亮度不高,如果需要较低亮度,可以使用这个配合定时器时间实现)

//Data:数据数组0位显示在最左边 Spot_P:为从左向右数点的位置
void Tube_Send_8Bit(Tube_Val_Typedef *Data,uint8_t Spot_P)
{
	for(int i=0;i<8;i++)
	{
		if(i==Spot_P)
		{
			Tube_Out(i,Data[i],ENABLE);
		}
		else
		{
			Tube_Out(i,Data[i],DISABLE);
		}
		
	}
}

发送8位数据,需要按照配合定时器,需要多次调用此函数
和上面那个函数不同的是,本函数的每位点亮周期为定时器中断的产生 周期,因此亮度高,能基本上接近一位点亮时亮度,但频率较低时会出现肉眼可见的闪烁

//定时器扫描用
void Tube_Send_Scan(Tube_Val_Typedef *Data,uint8_t Spot_P)
{
	static char i=0;
	if(i==Spot_P)
	{
		Tube_Out(i,Data[i],ENABLE);
	}
	else
	{
		Tube_Out(i,Data[i],DISABLE);
	}
	i++;
	if (i>7)
	{
		i=0;
	}
	Tube_Show();
}

中断服务函数(换定时器时要改这个名称)

//中断服务函数 扫描8个数码管
void TIM3_IRQHandler(void)
{
	if( TIM_GetITStatus(Tube_Tim,TIM_IT_Update)  != RESET)
	{
		Tube_Send_Scan(Tube_Val,Tube_Spot_P);
		// Tube_Send_8Bit(Tube_Val,Tube_Spot_P);
		TIM_ClearITPendingBit(Tube_Tim,TIM_IT_Update);
	}
}

两种扫描方式的示例都放进去了,了可以尝试下,根据需要设置

成品

已经打包为头文件,所有文件均可免费下载
CSDN

链接:百度网盘
提取码:82ig
演示图片

模式2(低亮)
在这里插入图片描述

模式2(高亮)
在这里插入图片描述

注意:这个模块的供电是3.3到5v均可,一定要注意电源和信号的匹配,别用5v供电给模块,3.3的高低电平,很可能无法驱动

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
以下是使用74HC595驱动四位数码管STM32F103代码: ```c #include "stm32f10x.h" #define DATA_PIN GPIO_Pin_7 #define LATCH_PIN GPIO_Pin_6 #define CLOCK_PIN GPIO_Pin_5 void HC595_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = DATA_PIN | LATCH_PIN | CLOCK_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); } void HC595_WriteByte(unsigned char data) { GPIO_ResetBits(GPIOB, LATCH_PIN); for (int i = 0; i < 8; i++) { if (data & 0x80) GPIO_SetBits(GPIOB, DATA_PIN); else GPIO_ResetBits(GPIOB, DATA_PIN); GPIO_SetBits(GPIOB, CLOCK_PIN); GPIO_ResetBits(GPIOB, CLOCK_PIN); data <<= 1; } GPIO_SetBits(GPIOB, LATCH_PIN); } void HC595_DisplayDigit(unsigned char digit, unsigned char position) { static const unsigned char digitCode[] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 0x77, // A 0x7C, // b 0x39, // C 0x5E, // d 0x79, // E 0x71 // F }; unsigned char code = digit < sizeof(digitCode) ? digitCode[digit] : 0; code |= position << 6; HC595_WriteByte(code); } void HC595_DisplayNumber(unsigned int number) { unsigned char digit[4]; digit[3] = number % 10; digit[2] = (number / 10) % 10; digit[1] = (number / 100) % 10; digit[0] = (number / 1000) % 10; HC595_DisplayDigit(digit[0], 0); HC595_DisplayDigit(digit[1], 1); HC595_DisplayDigit(digit[2], 2); HC595_DisplayDigit(digit[3], 3); } ``` 使用方法: ```c int main(void) { HC595_Init(); while (1) { for (int i = 0; i < 10000; i++) { HC595_DisplayNumber(i); for (int j = 0; j < 100000; j++); } } } ``` 其中,`HC595_DisplayDigit`函数用于在指定位置显示一个数字,`HC595_DisplayNumber`函数用于在四个位置上分别显示一个数字。在`main`函数中,可以使用`HC595_DisplayNumber`函数不断循环显示数字。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值