文章目录
- 基于stm32f1xx的四位数码管小数显示实验
- 一、实验目的:
- 1.1 IO分配
- 1.2 GPIO相关
-
- 1.2.1 GPIO初始化
- 1.2.2 GPIO常用置高置低函数(以PB5为例)
- 二、数四位码管实验主体
-
- 2.1 实现单个任意数字的显示
-
- 2.1.1 定义数字表以及数码管段选表
- 2.1.2 消影
- 2.1.3 主体代码如下
- 2.2 数据处理
-
- 2.2.1 记录小数点位置
- 2.2.2浮点数变为整数,并限制为四位
- 2.2.3 整数变为整数型数组
- 2.2.4 放入小数点
- 2.2.5 数据处理代码
- 2.3 数码管显示浮点数
- 三、结果呈现
- 四、完整代码
基于stm32f1xx的四位数码管小数显示实验
一、实验目的:
显示0-9999的任意数字(包括小数)
那为什么要做这个项目呢,其实因为很多传感器读出来的数据都是浮点数类型的数据,如超声波模块,温度传感器,压力传感器。为了显示结果,可以采用串口打印,数码管,oled或者lcd屏等,其中数码管的成本较低,因此本次实验采用了5461AS-1型号的数码管。
这个数码高管是位选高电平,段选低电平亮
1.1 IO分配
功能 | IO | 功能 | IO |
---|---|---|---|
w1 | PD0 | c | PE10 |
w2 | PD1 | d | PE11 |
w3 | PD2 | e | PE12 |
w4 | PD3 | f | PE13 |
a | PE8 | g | PE14 |
b | PE9 | h | PE15 |
1.2 GPIO相关
本次实验GPIO只用到了输出功能,STM32的所有输出功能如下所示:
代码 | 含义 |
---|---|
GPIO_Mode_Out_OD | 开漏输出 |
GPIO_Mode_Out_PP | 推挽输出 |
GPIO_Mode_AF_OD | 复用开漏输出 |
GPIO_Mode_AF_PP | 复用推挽输出 |
显而易见,AF两个是在复用功能中使用的,那么就在开漏输出和推挽输出中选择GPIO的模式
-
开漏输出:IO的高低电平由外部电路决定
-
推挽输出:IO的高低电平由程序决定
很显然,本次采用推挽输出即GPIO_Mode_Out_PP;
1.2.1 GPIO初始化
- 使能IO口时钟
- 定义GPIO结构
- GPIO参数填写
- 初始化GPIO
void gpio_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOD, ENABLE); // 使能PB端口与PD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15; // PE8->a
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOE, &GPIO_InitStructure); // 根据设定参数初始化PE8-PE15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; // PD0->w1
GPIO_Init(GPIOD, &GPIO_InitStructure); // 根据设定参数初始化PD0-PD3
}
1.2.2 GPIO常用置高置低函数(以PB5为例)
GPIO_ResetBits(GPIOB, GPIO_Pin_5); // 置低-0
PBout(5)=0;
GPIO_SetBits(GPIOB, GPIO_Pin_5); // 置高-1
PBout(5)=1;
当确定了端口以后,建议用第二种方式去写。
二、数四位码管实验主体
编辑器:Keil5+CLion
Keil5:单片机程序编写
CLion:数据处理程序编写(因为常用它的Pycharm,很喜欢它的风格)
2.1 实现单个任意数字的显示
- 选择第几个数码管(下文会叫段选)
- 选择展示什么数字(下文会叫位选)
以第二个数码管展示数字六为例:
在前面实验目的中说明了该数码管是段选是低电平选中,位选是高电平选中。这是由内部电路决定的,大家拿到一个数码管的时候可以有两种方式去确定段选与位选的高低特性:
- 查看这个数码管型号的内部电路
- 用单片机的5V和GND去挨个测试,这样既可以测试出每个管脚对应数码管的哪一部分,也可以确定高低特性。
选择第二个数码管:
PDout(0) = 1;
PDout(1) = 0;
PDout(2) = 1;
PDout(3) = 1;
展示六的数字:
PEout(8) = 1;
PEout(9) = 0;
PEout(10) = 1;
PEout(11) = 1;
PEout(12) = 1;
PEout(13) = 1;
PEout(14) = 1;
PEout(15) = 0;
因此可以将段选与位选定义两个数组,用管脚数字作为数组里的数据,这也是为什么IO分配时段选的管脚都是PD,位选的管脚都是PE。
由段选为例:
第X个数码管对应数据[4] [4] = { {0,1,1,1},{1,0,1,1},{1,1,0,1},{1,1,1,0}};
段选[4] = {0,1,2,3};
则利用for循环则可以一一赋值
2.1.1 定义数字表以及数码管段选表
// 四位数码管位选表
int table[20][8] = { { 1,1,1,1,1,1,0,0},// 0-9 数字表码
{ 0,1,1,0,0,0,0,0},
{ 1,1,0,1,1,0,1,0},
{ 1,1,1,1,0,0,1,0},
{ 0,1,1,0,0,1,1,0},
{ 1,0,1,1,0,1,1,0},
{ 1,0,1,1,1,1,1,0},
{ 1,1,1,0,0,0,0,0},
{ 1,1,1,1,1,1,1,0},
{ 1,1,1,1,0,1,1,0},
{ 1,1,1,1,1,1,0,1},// 0.-9. 小数数字表码
{ 0,1,1,0,0,0,0,1},
{ 1,1,0,1,1,0,1,1},
{ 1,1,1,1,0,0,1,1},
{ 0,1,1,0,0,1,1,1},
{ 1,0,1,1,0,1,1,1},
{ 1,0,1,1,1,1,1,1},
{ 1,1,1,0,0,0,0,1},
{ 1,1,1,1,1,1,1,1},
{ 1,1,1,1,0,1,1,1}};
// 四位数码管段选表
int channel_pin[4][4]= { { 0,1,1,1},{ 1,0,1,1},{ 1,1,0,1},{ 1,1,1,0}}; // 选择某一位对应的电平数组
2.1.2 消影
消影是数码管必须要的一个环节,那什么是消影呢?其实就是消除上一次显示留下来的数字残留,大家可以分别烧入无消影代码与有消影代码进行测试,显示多个不同数字,会发现,无消影的代码后续显示会出错。因此消影是不可缺的一部分。
原理如下:关闭所有段选,关闭所有位选,简单说就是啥也不亮。
2.1.3 主体代码如下
void ShowNum(int w, int num)
{
int j;
int weixuan[4] = { w1,w2,w3,w4}; // 位选数组
int duanxuan[8] = { a,b,c,d,e,f,g,h}; // 段选数组
// 消除重影
// 清除位选
for(j=0;j<4;j++)
PDout(weixuan[j]) = 1;
// 清除段选
for(j=0;j<8;j++)
PEout(duanxuan[j]) = 0;
// 选择第几个数码管亮
for(j=0;j<4;j++)
PDout(weixuan[j]) = channel_pin[w-1][j]; // w-1是因为数组的首元素是0,而传入的位是1
// 选择显示什么数字
for(j=0; j<8;j++)
PEout(duanxuan[j]) = table[num][j];
}
2.2 数据处理
由1的代码显然,假设显示13.78,因此
ShowNum(1,1);ShowNum(2,13);ShowNum(3,7);ShowNum(4,8);根据思路综合为以下的代码
num[4] = { 1,13,7,8};
for(i=0;i<4;i++) // 四位数码管显示数字
{
ShowNum(i+1,num[i]); // i+1是因为数组的首元素是0,而传入的位是1
}
其中13这个数字是因为在2.1.1定义数字表时,有小数点的数字与纯数字的位置相差了10。
由此可见,我们需要将浮点数变为四位整数型数组
可分为以下几步:
- step1:记录小数点位置,
- step2:float—int(13.78 - 1378),并限制位数为四位
- step3:1378-{1,3,7,8}
- step4:{1,3,7,8}->{1,13,7,8},放入小数点
2.2.1 记录小数点位置
由于四位数码管数据范围根据小数点可分为以下几类
{ 0 < n u m < 10 第一位数码管小数点,即数组第1个数字+10 10 ≤ n u m < 100 第二位数码管小数点,即数组第2个数字+10 100 ≤ n u m < 1000 第三位数码管小数点,即数组第3个数字+10 1000 ≤ n u m < 10000 第四位数码管小数点,无需点亮小数点 10000 ≤ n u m 超出范围,显示9999 \begin{cases} 0<num<10\text{第一位数码管小数点,即数组第1个数字+10}\\ 10 \leq num<100 \text{第二位数码管小数点,即数组第2个数字+10}\\ 100 \leq num<1000\text{第三位数码管小数点,即数组第3个数字+10}\\ 1000 \leq num<10000\text{第四位数码管小数点,无需点亮小数点}\\ 10000 \leq num\text{超出范围,显示9999} \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧0<num<10第一位数码管小数点,即数组第1个数字+1010≤num<100第二位数码管小数点,即数组第2个数字+10100≤num<1000第三位数码管小数点,即数组第3个数字+101000≤num<10000第四位数码管小数点,无需点亮小数点10000≤num超出范围,显示9999
可以用if语句进行分类实现。
2.2.2浮点数变为整数,并限制为四位
这个其实很简单,在上述分类中,以13.78568为例
13.78568 × 100 = 1378.568 13.78568 \times 100 = 1378.568 13.78568×100=1378.568
再将数字转换为int类型,既可以得到1378。同意在每个分类中只要更换乘数即可。
2.2.3 整数变为整数型数组
以1378为例
n u m = 1378 n = 1378 % 10 = 8 n u m = n / 10 = 137 n = 137 % 10 = 7 n u m = n / 10 = 13 num = 1378\\ n = 1378 \% 10 = 8\\ num = n/10 = 137\\ \\ n = 137\% 10 = 7\\ num = n/10 = 13 num=1378n=1378%10=8num=n/10=137n=137%10=7num=n/10=13
综上可得,每一位数字是倒序取出,由于在每个判断中都会用到这个,因此将它写为一个函数,返回为int类型的数组
void NumList(int *num_list,int num)
{
int i, temp=0;
for(i=0;i<4;i++)
{
temp = num%10; // 倒着取余数
num = num/10; // 去掉末尾
num_list[4-i-1] = temp; // 存入数组
}
上面说了,返回int类型的数组,那为什么是void呢?其实num_list这个变量就是返回的数组。
在C语言里返回数组有两种方式:
- malloc去申请动态内存返回,必须要free,不然会占据内存
- 需要返回的数组当作参数传入首地址,在函数中直接对其进行修改
这边使用的是第二种方法,因为单片机内存有限,一旦用malloc函数但是缺少了free后,则会占据内存最终导致单片机崩溃。
2.2.4 放入小数点
根据公式2,直接对2.2.3返回的数组进行修改即可。
2.2.5 数据处理代码
传入浮点数,返回整数型数组,当然返回形式依然用上述的第二种方式。
void DisplayNumList(int *num_list_d, float num)
{
int n,i;
// 判断浮点数字的小数点位置
if(0<num && num<10)
{
n = num*1000;
NumList(num_list_d,n);
num_list_d[0] += 10; // 加10是因为在位选码中,3与3.的位置相差了10
}
if(10<=num && num<100)
{
n = num*100;
NumList(num_list_d,n);
num_list_d[1] += 10;
}
if(100<=num && num<1000)
{
n = num*10;
NumList(num_list_d,n);
num_list_d[2] += 10;
}
if(1000<=num && num<10000)
{
n = num;
NumList(num_list_d,n);
}
// 超出最大数字9999时显示9999
if(10000 < num )
{
for(i=0;i<4;i++)
num_list_d[i] = 9;
}
}
2.3 数码管显示浮点数
void Display(float num_f)
{
int i,t;
int num[4] = { 0}; // 接收处理浮点型数字后的数组,要初始化
DisplayNumList(num, num_f); // 传入浮点数子,返回处理后的数组
for(t=0;t<10000;t++) // 延时是为了增加显示时间,经测试t>6000为好
{
for(i=0;i<4;i++) // 四位数码管显示数字
{
ShowNum(i+1,num[i]); // i+1是因为数组的首元素是0,而传入的位是1
}
}
}
根据之前所有的思路,其实就是一个最终的整合,但是这里可以发现在外层嵌套了一个很大的for循环,这样做的目的是什么的?
延长显示时间,数码管里绝对不能出现delay函数,因为delay函数就是什么也不干,干等着,因此为了延长显示时间,则对显示函数进行for循环即可,若是需要更长的时间,可以多层循环进行嵌套。
三、结果呈现
四、完整代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
// 函数定义
void NumList(int *num_list,int num); // 4位整数型数字变整数型数组
void DisplayNumList(int *num_list_d, float num); // 浮点数变为四位数码管可识别的整数型数组
void gpio_Init(void); // IO口初始化函数
void ShowNum(int w, int num); // 四位数码管显示任意端口任意数字
void Display(float num_f); // 最终显示函数
// PDout(w1)位选定义
#define w1 0
#define w2 1
#define w3 2
#define w4 3
// PEout(a) 段选定义
#define a 8
#define b 9
#define c 10
#define d 11
#define e 12
#define f 13
#define g 14
#define h 15
// 四位数码管位选表
int table[20][8] = { { 1,1,1,1,1,1,0,0},// 0-9 数字表码
{ 0,1,1,0,0,0,0,0},
{ 1,1,0,1,1,0,1,0},
{ 1,1,1,1,0,0,1,0},
{ 0,1,1,0,0,1,1,0},
{ 1,0,1,1,0,1,1,0},
{ 1,0,1,1,1,1,1,0},
{ 1,1,1,0,0,0,0,0},
{ 1,1,1,1,1,1,1,0},
{ 1,1,1,1,0,1,1,0},
{ 1,1,1,1,1,1,0,1},// 0.-9. 小数数字表码
{ 0,1,1,0,0,0,0,1},
{ 1,1,0,1,1,0,1,1},
{ 1,1,1,1,0,0,1,1},
{ 0,1,1,0,0,1,1,1},
{ 1,0,1,1,0,1,1,1},
{ 1,0,1,1,1,1,1,1},
{ 1,1,1,0,0,0,0,1},
{ 1,1,1,1,1,1,1,1},
{ 1,1,1,1,0,1,1,1}};
// 四位数码管段选表
int channel_pin[4][4]= { { 0,1,1,1},{ 1,0,1,1},{ 1,1,0,1},{ 1,1,1,0}}; // 选择某一位对应的电平数组
void NumList(int *num_list,int num)
{
int i, temp=0;
for(i=0;i<4;i++)
{
temp = num%10; // 倒着取余数
num = num/10; // 去掉末尾
num_list[4-i-1] = temp; // 存入数组
}
}
void DisplayNumList(int *num_list_d, float num)
{
int n,i;
// 判断浮点数字的小数点位置
if(0<num && num<10)
{
n = num*1000;
NumList(num_list_d,n);
num_list_d[0] += 10; // 加10是因为在位选码中,3与3.的位置相差了10
}
if(10<=num && num<100)
{
n = num*100;
NumList(num_list_d,n);
num_list_d[1] += 10;
}
if(100<=num && num<1000)
{
n = num*10;
NumList(num_list_d,n);
num_list_d[2] += 10;
}
if(1000<=num && num<10000)
{
n = num;
NumList(num_list_d,n);
}
// 超出最大数字9999时显示9999
if(10000 < num )
{
for(i=0;i<4;i++)
num_list_d[i] = 9;
}
}
void gpio_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOD, ENABLE); // 使能PB端口与PD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15; // PE8->a
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHz
GPIO_Init(GPIOE, &GPIO_InitStructure); // 根据设定参数初始化PE8-PE15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3; // PD0->w1
GPIO_Init(GPIOD, &GPIO_InitStructure); // 根据设定参数初始化PD0-PD3
}
void ShowNum(int w, int num)
{
int j;
int weixuan[4] = { w1,w2,w3,w4}; // 位选数组
int duanxuan[8] = { a,b,c,d,e,f,g,h}; // 段选数组
// 消除重影
// 清除位选
for(j=0;j<4;j++)
PDout(weixuan[j]) = 1;
// 清除段选
for(j=0;j<8;j++)
PEout(duanxuan[j]) = 0;
// 选择第几个数码管亮
for(j=0;j<4;j++)
PDout(weixuan[j]) = channel_pin[w-1][j]; // w-1是因为数组的首元素是0,而传入的位是1
// 选择显示什么数字
for(j=0; j<8;j++)
PEout(duanxuan[j]) = table[num][j];
}
void Display(float num_f)
{
int i,t;
int num[4] = { 0}; // 接收处理浮点型数字后的数组,要初始化
DisplayNumList(num, num_f); // 传入浮点数子,返回处理后的数组
for(t=0;t<10000;t++) // 延时是为了增加显示时间,经测试t>6000为好
{
for(i=0;i<4;i++) // 四位数码管显示数字
{
ShowNum(i+1,num[i]); // i+1是因为数组的首元素是0,而传入的位是1
}
}
}
int main(void)
{
float num_f; // 定义变量,必须放在最前面
gpio_Init(); // IO初始化
delay_init(); // 延时函数初始化
// 主循环函数
while(1)
{
num_f = 13.78;
Display(num_f);
num_f = 25.96;
Display(num_f);
}
}