uC/OS室温计

#5 uC/OS室温计

这个实验的目的是理解uC/OS II的任务调度方式,编写uC/OS II的应用程序,通过寄存器直接操纵GPIO来驱动外 部设备。

##配合课程

RTOS

##实验目的

学习uC/OS II的应用程序编写;
理解如何直接操纵GPIO,体会与Linux的不同;
学习单总线设备的访问方式;
学习7段数码管的时分复用驱动方式。

##实验器材

###硬件

STM32F103板一块;
USB串口板一块;
面包板一块;
两位7段数码管(共阳)一颗;
360Ω 1/8W电阻2颗;
DHT-11 温湿度传感器1个;
面包线若干。

###软件

####编译软件;
Fritzing。
uvison5.

#实验步骤

设计输出方案,画连线示意图;
在面包板上连线,完成外部电路;
编写C/C++程序,测试程序和电路;

  1. 测试、实现uC/OS II对GPIO的访问;
  2. 实现DHT-11数据的读;
  3. 实现以时分复用方式在四位7段数码管上依次显示0000-9999的数字;
  4. 用两个uc/OS II任务,一个定时读DHT-11数据,一个轮流驱动数码管,一秒一次显示当前温度和湿度。注意处理好 两个任务之间的数据共享。

#连线示意图
segment

seg

以上为四位共阳数码管的引脚图片
seg

以上为四位共阳数码管的接线图片,由于在fritzing中没有找到合适的stm32板子,所以将这块25pin的引脚除去下面的6个pin就当它做STM32F103了,在实体连线中,将数码管COM1-4分别连接到了C15-14,A0-A1,其余脚按照顺时针顺序连接到了STM32板的两端。
dht
如图为dht的连线图,在本次试验中,Vcc接3.3V,Dout接A8,GND接地,为了读数稳定,需要在地线和Dout间连接一个上拉电阻。

#实验过程

###UCOS的移植

在UCOS移植的过程中参考了网上的资料。
IDE环境:uvision5, UCOS源码
文章来源sina blog

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
1.新建ucos工程,选择STM32F103VE,选择CMSIS下的CORE和Device下的Startup,以及Device下的StdPeriph Drivers下的Framework,RCC,和GPIO
2.工程中和实际目录中都新建几个目录,APP,UCOS,BSP,LIB,CPU,Output
3.工程上右键,Options,Output页签,Select Foldeer for Objects,进入Output目
录,点击OK
4.把Micrium\Software\uCOS-II\Source目录中的文件拷贝到UCOS目录下,并添加到工程

5.工程Options中,C/C++页签,Include Paths,点击后面省略号可选择include目录,添
加UCOS路径
6.复制Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\OS-Probe目录下
的文件app_cfg.h,os_cfg.h和includes.h到APP目录中,并在Include Paths中添加
APP
7.复制Micrium\Software\uCOS-II\Ports\arm-cortex-m3\Generic\RealView目录
下的所有文件到CPU目录,添加到工程和Include Path中
8.工程Options中,C/C++页签,Defines中添加 USE_STDPERIPH_DRIVER
9.把RTE和RTE\Device\STM32F103VE添加进Include Paths中
10.修改os_cfg.h文件,#define OS_APP_HOOKS_EN 1为0
11.BSP目录下新建BSP.c文件,添加内容如下:
#include

CPU_INT32U BSP_CPU_ClkFreq (void)
{
RCC_ClocksTypeDef rcc_clocks;

RCC_GetClocksFreq(&rcc_clocks);

return ((CPU_INT32U)rcc_clocks.HCLK_Frequency);
}

INT32U OS_CPU_SysTickClkFreq (void)
{
INT32U freq;

freq = BSP_CPU_ClkFreq();
return (freq);
}
12.复制Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\BSP目录下的bsp.h到 BSP目录中
13.复制Micrium\Software\uC-CPU\ARM-Cortex-M3\RealView目录和Micrium\Software\uC-CPU目录下的所有文件到CPU目录下,并添加到工程和Include Path中
14.复制Micrium\Software\uC-LIB目录下的所有.h文件到LIB目录下,并添加到Include
Path中
15.注释掉bsp.h中的#include 和#include
16.app_cfg.h文件中,修改为#define APP_OS_PROBE_EN 0
17.APP目录下新建app.c文件,内容为
#include

int main(){
OSInit();
OSStart();
return 0;
}

18.注释掉includes.h文件中的#include 和#include

在app.c中还需要添加如下时钟中断处理函数:

1
2
3
4
5
6
7
8
9
10
void SysTick_handler(void){
OS_CPU_SR cpu_sr;

OS_ENTER_CRITICAL();
OSIntNesting++;
OS_EXIT_CRITICAL();

OSTimeTick();
OSIntExit();
}

这样全部做完之后代码结构如图:
代码结构

###GPIO的访问
首先,在GPIO初始化函数中,初始化所有我们需要的GPIO,当然这里是全部要用到的都初始化了。

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
void GPIO_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStructure;

RCC_DeInit();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA, ENABLE);


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14
| GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11
| GPIO_Pin_12;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_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_Init(GPIOA, &GPIO_InitStructure);
}

随便点个小灯来测试一下吧(其实是做完后面的反过来做得这个):

1
GPIO_WriteBit(GPIO_C,GPIO_Pin_14,RESET);

###时分复用的八位共阳数码管
首先为了在单独的一位数字上显示出我们想要的数字,要根据数码管每一位所对应的引脚输入正确的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned char table[10][8] =
{
{0, 0, 1, 1, 1, 1, 1, 1}, //0
{0, 0, 0, 0, 0, 1, 1, 0}, //1
{0, 1, 0, 1, 1, 0, 1, 1}, //2
{0, 1, 0, 0, 1, 1, 1, 1}, //3
{0, 1, 1, 0, 0, 1, 1, 0}, //4
{0, 1, 1, 0, 1, 1, 0, 1}, //5
{0, 1, 1, 1, 1, 1, 0, 1}, //6
{0, 0, 0, 0, 0, 1, 1, 1}, //7
{0, 1, 1, 1, 1, 1, 1, 1}, //8
{0, 1, 1, 0, 1, 1, 1, 1} //9
};

以上的代码分别对应了0-9的数字在数码管上的显示IO管脚电平,按照顺时针方向,从8字顶端开始为ABCDEFG,DP,而代码中从高到低对应DP,GFEDCBA。这样就可以在数码管上显示单个数字了,为了保险起见,可以给数码管接个电阻避免烧坏。

为了同时点亮四个数字,利用人眼的余晖效应,只要快速切换点亮每个数字,在人眼看来就像是同时亮起来的一样。这时就需要时分复用技术了。对于共阳数码管,每个数字当我们想让他亮的时候,将对应的阳极置为高电平即可,如下为选择数码管位数的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void digit_choose(int index){
BitAction v[4];
int i;
for (i=0; i<4; i++){
if (index == i){
v[i] = Bit_SET;
}else{
v[i] = Bit_RESET;
}
}
GPIO_WriteBit(GPIOA, GPIO_Pin_11, v[0]);
GPIO_WriteBit(GPIOA, GPIO_Pin_12, v[1]);
GPIO_WriteBit(GPIOC, GPIO_Pin_14, v[2]);
GPIO_WriteBit(GPIOC, GPIO_Pin_15, v[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
25
26
void display(int digit){
static int index=-1;
int i;
int base=1000;
index=(index+1)%4;
for (i=0;i<index;i++){
base/=10;
}
digit=(digit/base)%10;
digit_choose(index);//选择数位
digit_output(digit,0);//利用定义好的管脚参数显示一个数字
}
...
main(){
...
count=0;
while(1)
{
count++;
for (int i=0;i<4;i++){
display(count/10);//为了让数字增长不过快
Delay_ms(5);
}
}
...
}

这样就可以在数码管上显示数字0-9999了。

###DHT11温湿计使用

DHT11是一种单总线的传感器,这意味着它的输入和输出引脚是公用的。这样就需要以精确的时间给它发送信号,它的开启模式是这样的:
dht

1
2
3
4
总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证
DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送
80us低电平响应信号.主机发送开始信号结束后,延时等待20-40us后,读取DHT11的响应信号,
主机发送开始信号后,可以切换到输入模式,或者输出高电平均可,总线由上拉电阻拉高。

DHT11的响应信号为将电平拉高,通常拉高80μs后就会开始传输数据。数据格式如下:

1
2
3
4
5
6
数据格式:8bit湿度整数数据
+8bit湿度小数数据
+8bi温度整数数据
+8bit温度小数数据
+8bit校验和 数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温
度整数数据+8bit温度小数数据”所得结果的末8位。

这样就可以在GPIO A8口读到DHT11送来的数据了。
在程序中我们需要编写将DHT11激活的函数:

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
void DHT11_Set(int state){//设置数据读取引脚电平
BitAction s;
if (state){
s = Bit_SET;
}else{
s = Bit_RESET;
}
GPIO_WriteBit(GPIOA, GPIO_Pin_8, s);
}

void DHT11_Pin_OUT(){//将数据引脚设置为主机输出并拉高
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DHT11_Set(1);
}

uint8_t DHT11_Check(){
return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8);
}

void DHT11_Wait(int state){
while(DHT11_Check()!=state){

}
}

void DHT11_Pin_IN(){//将数据引脚设置为输入并拉高
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DHT11_Set(1);
}

void DHT11_Rst(){
DHT11_Pin_OUT();//置为输出
DHT11_Set(0);//拉低给初始信号
Delay_us(25000);//延迟大于18ms
DHT11_Set(1);//拉高
Delay_us(30);
DHT11_Set(0);//转为输入,等待DHT11的信号
DHT11_Pin_IN();
}
...
main()
{
...
DHT11_Rst();
if (DHT11_Check()==0){
DHT11_Wait(1);
DHT11_Wait(0);//DHT拉低准备输出
...
}
...
}
...

激活后再按照传入的数据格式编写一下读取数据的函数即可读取DHT11传来的数据了。

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
uint8_t DHT11_Read_Byte(){
int i, cnt;
uint8_t data = 0;
for (i=0; i<8; i++){
cnt = 0;
data <<= 1;//每次读一位数据
DHT11_Wait(1);
while (DHT11_Check() > 0){//多次读取
Delay_us(1);
cnt++;
}
data |= cnt > 5;
}
return data;
}

uint8_t DHT11_Read_Data(uint8_t *buf){
int i;
int cpu_sr;
OS_ENTER_CRITICAL();//进入critical区,避免抢占
DHT11_Rst();
if (DHT11_Check() == 0){
DHT11_Wait(1);
DHT11_Wait(0);
for (i=0; i<5; i++){
buf[i] = DHT11_Read_Byte();
}
DHT11_Pin_OUT();//重置数据引脚
OS_EXIT_CRITICAL();
if (buf[0] + buf[1] + buf[2] + buf[3] == buf[4]){//检验校验和
return 1;
}else{
return 0;
}
}else{
OS_EXIT_CRITICAL();
return 0;
}
}

至此已经可以读取DHT11的数据了。

###双任务执行

使用UCOS来执行两个任务其实非常方便,只需要使用UCOS开启两个进程即可。

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
void gainData(void * data)
{
uint8_t buf[5];
memset(buf,0,sizeof(buf));
while(1){
if (DHT11_Read_Data(buf)==1)
{
if (flag==0)
ledValue=buf[2];//湿度
else
ledValue=buf[0];//温度
flag=!flag;
}
Delay_ms(1000);
}
}
void ToSegment(void * data)
{
while(1){
display(ledValue);
Delay_ms(5);
}
}
#define size 100
int STK1[size],STK2[size];
int main()
{
...
OSInit();
OS_CPU_SysTickInit();
OSTaskCreate(gainData,(void*)0,(OS_STK *)&STK1,1);
OSTaskCreate(ToSegment,(void*)0,(OS_STK *),&STK2,2);
OSStart();
...
}

运行结果为: