文章目录
- 基于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只使用输出功能,STM所有输出功能如下:
代码 | 含义 |
---|---|
GPIO_Mode_Out_OD | 开漏输出 |
GPIO_Mode_Out_PP | 推挽输出 |
GPIO_Mode_AF_OD | 复用开漏输出 |
GPIO_Mode_AF_PP | 复用推挽输出 |
显而易见,AF两者在复用功能中使用,然后选择泄漏输出和推拉输出GPIO的模式
显然,推挽输出是这次使用的GPIO_Mode_Out_PP;
1.2.1 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)
{
/* w: 传入需要显示的位置(2) num : 传入需要显示的数字(8) */
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)
{
/* num: 传入的四位整型数字(1378) *num_list : 需要返回的整型数组({1,3,7,8}) */
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)
{
/* num: 传入的浮点型型数字(13.78) *num_list_d : 需要返回的整型数组({1,13,7,8}) */
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) { /* num_f : 传入需要显示的数字(13.78) */ int i,t; int num[4] = {