0%

嵌入式课程作业:流水灯+数码管+多线程

2020春季学期《嵌入式系统设计》课程作业

一、八位流水灯设计

1. 任务需求

电路如图所示,GPIOA管脚低8位连接至LD。请循环依次点亮LD0~LD7,每个LD亮灭时间间隔200ms。(代码中无需初始化GPIOA,请直接写出流水亮灭逻辑伪代码,可直接给GPIOA赋值操作)

UJfYMq.png

2.问题分析

由电路图和任务需求可知,八个LED灯采用共阴极接法分别,LED 0-7 的阳极分别与MCU的GPIO引脚的A0-A7相连,通过以此使能GPIOA的0-7八个引脚依次输出高低电平(GPIO_SetBits,GPIO_ResetBits)实现流水灯效果。

3.流水灯伪代码书写(主函数部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*

*/
int main()
{
//初始化部分

LED_Init(); //LED初始化,在LED.c和LED.h中,同时保证开始时八个引脚为低电平
SysTick_Init(72); //初始化时钟,调用delay_ms()函数实现精准延时。
u16 LED_PORTA[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}; //利用LED_PORTA数组存储8个引脚

//主循环体部分
while(1)
{
u8 i;
//for循环,实现八个引脚电压依次拉高拉低
for(i=0;i<8;i++)
{
GPIO_SetBits(LED_PORT,LED_PORTA[i]); //Ai(0-7)引脚高电平,点亮LED
delay_ms(200); //延时200ms
GPIO_ResetBits(LED_PORT,LED_PORTA[i]); //Ai(0-7)引脚低电平,熄灭LED
}
}
}

4.工程实现

笔者在Keil上利用stm32F103库函数模板实现流水灯的工程文件编写,主要任务是对Led.c及其头文件的编写实现对stm32 GPIOA引脚的初始化。工程目录如下:

UJozMd.png

led.h头文件

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _led_H
#define _led_H

#include "stm32f10x.h"

/*LED端口与引脚定义*/
#define LED_PORT GPIOA
#define LED_PIN (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8)
#define LED_PORT_RCC RCC_APB2Periph_GPIOA

void LED_Init(void);
#endif

led.c文件

1
2
3
4
5
6
7
8
9
10
11
12
#include "led.h"

void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);
GPIO_InitStructure.GPIO_Pin=LED_PIN; //设置IO口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//设置推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//设置传输速率
GPIO_Init(LED_PORT,&GPIO_InitStructure);
GPIO_ResetBits(LED_PORT,LED_PIN); //根据共阴极解法,初始化低电平,全部熄灭
}

5.实验搭建及效果

笔者手头上有一块stm32F103RCT6开发板以及LED小灯若干,所以索性搭建了流水灯实现的硬件电路,其中RCT6需要借助USB转TTL将hex文件烧录到板子中,根据实验原理在面包板上搭建电路,成功实现了LED流水灯效果

UJbySU.png

二、SN74HC595 驱动led设计

1.任务需求

电路如图所示,一片SN74HC595DR串并转换芯片连接至8段数码管。请驱动LED,循环显示09数字,显示间隔时间1s。其中74HC595的三个GPIO选择PB0PB3。

UJqdje.png

2. 问题分析

本题主要是利用74HC595带锁存移位寄存器串入并处芯片实现将3个GPIO引脚的输入转化为8个并行输出来驱动数码管显示数字。

3. 知识回顾

  • 74HC595工作原理

74HC595芯片管脚的实物图和接线图如图所示,左右各有8个引脚共有16个引脚,其中Q0-Q8用作并行输出,在本题中将连接数码管的八个引脚。一个电源引脚Vcc和一个接地GND。另外,在本题中将MR和OE分别连接电源和地。

Ds(SER)引脚是串行数据输入引脚,连接GPIOB2,根据B2引脚电平的高低将输入1和零;SHcp(SCLK)引脚为输入开关,连接GPIOB1,当该引脚遇到上升沿时将输入数据传送到寄存器中。STcp(RCLK)引脚为输出开关,连接GPIOB0,当该引脚遇到上升沿时使得寄存器中8路并行口输出。

UtZ3jg.png

下面的动图更加形象地说明595芯片的工作过程

  • 八段数码管介绍

八段数码管的基本原理为:将八个LED灯采用共阳极(或共阴极)的接法,然后通过控制每个LED小灯另一端电平的高或低来实现每个灯的亮与灭进而实现显示不同的数字或者字母。

UtR8te.png

数码管的字母对应表:以共阴极数码管为例,下面介绍一下0-9数字的字母对应表,根据自己的数码管实际情况,若为共阳极连接方式,则将0与1分别替换即可。

UtWeUS.png

4.工程实现

A.基于Stm32+keil实现

在流水灯的实验基础之上,在App目录下新建一个smg.c文件用于实现数码管的显示,smg.c的编写与led.c相似,基本上就是GPIO引脚的初始化。

smg.h头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef _smg_H
#define _smg_H

#include "system.h"

/*数码管端口引脚的定义*/
#define SMG_PORT GPIOB
#define SMG_PIN (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define SMG_PORT_RCC RCC_APB2Periph_GPIOB

void SMG_Init(void);//数码管的初始化函数

#endif

smg.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* title : SMG_Init函数
* Author : 李振宇
* date: : 2020.07.13
*/
void SMG_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; //声名结构体变量,初始化GPIO
/*开启GPIO时钟*/
RCC_APB2PeriphClockCmd(SMG_PORT_RCC,ENABLE);


/* 模式和输出方式配置 */
GPIO_InitStructure.GPIO_Pin=SMG_PIN; //GPIO端口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(SMG_PORT,&GPIO_InitStructure); /* 初始化GPIO*/
GPIO_SetBits(SMG_PORT,SMG_PIN); //默认为高电平,共阳极数码管,初始熄灭
}

编写主函的时候,首先定义一个10*9 的二维数组用于存放0-9 十个数字。在主循环体中按照一下步骤将数据逐位存入74HC595芯片

  1. 判断输入的为1 or 0
  2. 若为1 则将B2拉高,为0将B2拉低
  3. 将B1拉高,将B2的输入存入寄存器
  4. 再将B1拉低,等待下一次存入

main.c 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
* title : 数码管显示数字主函数
* Author : 李振宇
* date: : 2020.07.13
*/
#include "system.h"
#include "SysTick.h"
#include "smg.h"
u8 num1[10][8]={{1,1,0,0,0,0,0,0},
{1,1,1,1,1,0,0,1},
{1,0,1,0,0,1,0,0},
{1,0,1,1,0,0,0,0},
{1,0,0,1,1,0,0,1},
{1,0,0,1,0,0,1,0},
{1,0,0,0,0,0,1,0},
{1,1,1,1,1,0,0,0},
{1,0,0,0,0,0,0,0},
{1,0,0,1,0,0,0,0}};

int main()
{
//变量,时钟,数码管初始化
u8 i=0,j=0;
SysTick_Init(72);
SMG_Init();

while(1)
{
for(i=0;i<10;i++)
{
for(j=0;j<8;j++)
{
//1拉高0拉低
if(num1[i][j]==1) GPIO_SetBits(SMG_PORT,GPIO_Pin_2);
else GPIO_ResetBits(SMG_PORT,GPIO_Pin_2);

//GPIOB1上升沿;然后置零复位,为下一位数据准备
GPIO_SetBits(SMG_PORT,GPIO_Pin_1);
GPIO_ResetBits(SMG_PORT,GPIO_Pin_1);
}
//输出开关B0打开
GPIO_SetBits(SMG_PORT,GPIO_Pin_0);
//保持一秒
delay_ms(1000);
//重置B0输出开关,为下一个数字输出准备
GPIO_ResetBits(SMG_PORT,GPIO_Pin_0);
}

}
}

整个工程目录如下图所示:

UtxG4J.png

将工程生成的hex文件烧录到stm32F103RCT6中,笔者手头上现有一个八段数码管和一篇595芯片,根据上述接线方法连接电路,其中stm32的B0、B1、B2分别连接595芯片的数据发送端口Ds管脚,寄存器输入开关SHCP管脚以及并行输出开关STCP管脚,正常运行后可以看到如下显示效果:

UNx4TU.png

B.基于树莓派RPI.GPIO实现
树莓派的GPIO使用起来非常方便,直接通过编写一个py文件调用RPI.GPIO库和时间库就可以实现本实验需求,基本原理再次不做赘述,smg.py文件编写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# encoding: utf-8
import RPi.GPIO
import time

# 串行数据输入引脚连接的GPIO口
DS = 16 #PIN 36
# 移位寄存器时钟控制引脚连接的GPIO口
SHCP = 20 #PIN 38
# 数据锁存器时钟控制引脚连接的GPIO口
STCP = 21 #PIN 40

RPi.GPIO.setmode(RPi.GPIO.BCM) # 采用BCM编码
#设置GPIO输出模式
RPi.GPIO.setup(DS, RPi.GPIO.OUT)
RPi.GPIO.setup(STCP, RPi.GPIO.OUT)
RPi.GPIO.setup(SHCP, RPi.GPIO.OUT)
#初始化低SHCP STCP低电平
RPi.GPIO.output(STCP, False)
RPi.GPIO.output(SHCP, False)

# 通过串行数据引脚向74HC595的传送一位数据
def setBitData(data):
RPi.GPIO.output(DS, data)
# 制造一次移位寄存器时钟引脚的上升沿(先拉低电平再拉高电平)
# 74HC595会在这个上升沿将DS引脚上的数据存入移位寄存器D0
RPi.GPIO.output(SHCP, True)
RPi.GPIO.output(SHCP, False)

# 指定数码管显示数字num(0-9),第2个参数是显示不显示小数点(true/false)
# 由于我使用的数码管是共阳数码管,所以设置为低电平的段才会被点亮
# 如果是共阴数码管,那么要将下面的True和False全部颠倒过来,或者统一在前面加上not
def showDigit(num, showDotPoint):

if (num == 0) :
setBitData(not showDotPoint) # DP
setBitData(True) # G
setBitData(False) # F
setBitData(False) # E
setBitData(False) # D
setBitData(False) # C
setBitData(False) # B
setBitData(False) # A
elif (num == 1) :
setBitData(not showDotPoint)
setBitData(True)
setBitData(True)
setBitData(True)
setBitData(True)
setBitData(False)
setBitData(False)
setBitData(True)
elif (num == 2) :
setBitData(not showDotPoint)
setBitData(False)
setBitData(True)
setBitData(False)
setBitData(False)
setBitData(True)
setBitData(False)
setBitData(False)
elif (num == 3) :
setBitData(not showDotPoint)
setBitData(False)
setBitData(True)
setBitData(True)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)
elif (num == 4) :
setBitData(not showDotPoint)
setBitData(False)
setBitData(False)
setBitData(True)
setBitData(True)
setBitData(False)
setBitData(False)
setBitData(True)
elif (num == 5) :
setBitData(not showDotPoint)
setBitData(False)
setBitData(False)
setBitData(True)
setBitData(False)
setBitData(False)
setBitData(True)
setBitData(False)
elif (num == 6) :
setBitData(not showDotPoint)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(True)
setBitData(False)
elif (num == 7) :
setBitData(not showDotPoint)
setBitData(True)
setBitData(True)
setBitData(True)
setBitData(True)
setBitData(False)
setBitData(False)
setBitData(False)
elif (num == 8) :
setBitData(not showDotPoint)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)
elif (num == 9) :
setBitData(not showDotPoint)
setBitData(False)
setBitData(False)
setBitData(True)
setBitData(False)
setBitData(False)
setBitData(False)
setBitData(False)


# 拉高STCP并行发送数字
RPi.GPIO.output(STCP, True)
time.sleep(0.5)
RPi.GPIO.output(STCP, False)

try:
# 从0显示到9,不显示小数点
while(True):
for x in range(0,10):
showDigit(x, False)

# 再从0显示到9,显示小数点
for y in range(0,10):
showDigit(y, True)

except KeyboardInterrupt:
pass

# 最后清理GPIO口
RPi.GPIO.cleanup()

编写完程序后,在文件目录下指令执行smg.py文件

UNwRSK.png

笔者利用手上的树莓派4B的三个GPIO引脚连接595的DS、SHCP、STCP三个引脚,可以观察到如下实验现象:

UUCtq1.png

三、多线程设计

1.任务需求

多线程设计,每个线程各自打印一个字母,可以交替打印A, B, C字母。

2.解决思路

  1. 让三个线程对应三个标志flag:0,1,2。同时使得三个线程共享一个锁。
  2. 通过对状态标志进行判断,如果没有轮到当前线程打印时则通过wait()函数来阻塞当前线程,如果轮到当前状态标志,则执行打印操作。
  3. 执行完成后将状态标志flag修改为下一状对应的态标志并将打印次数加1,最后执行notify_all()唤醒当前所有阻塞进程。
  4. 三个线程打印次数达到设定值,三个子线程执行结束,跟随主线程一块儿退出

    3.程序实现

    笔者用C++编写程序如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #include <iostream>
    #include <thread>
    #include <condition_variable>
    using namespace std;

    mutex mtx; //互斥信号量
    condition_variable cv; //阻塞等待条件变量
    int flag = 0; //状态标志
    void PrintString_1()
    {
    unique_lock<mutex> lk(mtx); //获得一个锁
    int count = 0; //计数器
    while (count < 10) //循环打印次数10
    {
    while (flag != 0) //判断打印标志,不为零,则使得线程等待,处于阻塞等待,为0,跳出阻塞,执行打印操作
    cv.wait(lk);
    cout << this_thread::get_id() << " : " << "A" << std::endl;
    flag = 1; //打印完成后,状态标志变为1
    count++; //循环次数加一
    cv.notify_all(); //唤醒当前所有的线程,因为此时其他两个线程在wait,而且下一步就要去执行其中一个
    }
    }

    void PrintString_2()
    {
    unique_lock<mutex> lk(mtx);
    int count = 0;
    while (count < 10)
    {
    while (flag != 1)
    cv.wait(lk);
    cout << this_thread::get_id() << " : " << "B" << std::endl;
    flag = 2;
    count++;
    cv.notify_all();
    }
    }

    void PrintString_3()
    {
    unique_lock<mutex> lk(mtx);
    int count = 0;
    while (count < 10)
    {
    while (flag != 2)
    cv.wait(lk);
    cout << this_thread::get_id() << " : " << "C" << std::endl;
    flag = 0;
    count++;
    cv.notify_all();
    }
    }

    int main()
    {
    //创建三个线程,分别执行打印ABC
    std::thread t1(PrintString_1);
    std::thread t2(PrintString_2);
    std::thread t3(PrintString_3);
    t3.join();
    t1.join();
    t2.join();
    return 0;
    }
    运行结果如下(循环打印5次)

本文标题:嵌入式课程作业:流水灯+数码管+多线程

文章作者:Decaprio Lee

发布时间:2020年07月13日 - 15:07

最后更新:2020年07月19日 - 17:07

原始链接:http://yoursite.com/2020/07/13/024%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F%E4%BD%9C%E4%B8%9A/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

赞赏一杯咖啡
-------------------本文结束 感谢您的阅读-------------------