【STM32标准库】【自制库】8位8段数码管(74HC595)【软件部分】
用STM32F4驱动8位8段数码管
文章基于适用于STM32F4系列,作者使用STM32F401CCU6开发板。
本文章基于此系列和开发板展开讨论。
硬件部分
模块的介绍和硬件原理在这里
【基础知识】【模块介绍】8位8段数码管(74HC595)【硬件部分】
软件设计
需求分析
- 8位“同时”显示
- 数码管可以显示0-F的数值和横线作为分隔符
- 可以设置小数点位置
- 能动态修改要显示的值
- 显示过程中可以进行其他运算
编程思路
总体上使用硬件部分提到过的扫描,间隔一段时间分别点亮8位数码管,使用定时器中断产生一定的时间来减少CPU的占用。
- 字码数据编写
- 初始化(GPIO,NVIC,定时器)
- 数据发送程序
- 定时扫描程序
字码数据和宏定义
这是定义在头文件中,为了便于使用
这里使用了固件库的头文件和上次的延迟函数文件
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;
定义时使用的初值
初始化
GPIO,NVIC,定时器中断的配置之前介绍过了,点击查看详情。
定时器中断,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的高低电平,很可能无法驱动
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)