1.在PCB电路中添加AH20温湿度采集芯片
学习温湿度传感器原理,阅读国产温湿度采集芯片AHT20数据手册,在之前stm32最小系统电路原理中添加
AHT20数据采集原理电路,并完成PCB电路设计
AH20相关
传感器封装图
传感器引脚图
典型应用电路
PCB部分后续更新
2.在STM32下完成一个基于FreeRTOS的多任务程序
学习FreeRTOS原理,在STM32下完成一个基于FreeRTOS的多任务程序,执行3个周期性task,具体任务不限,但建议如下:task1,每间隔500ms闪烁(变化)一次LED;task2,每间隔2000ms,向串口发送一次指令数据“helloworld!";task3,每间隔5000ms,从AHT20采集一次温湿度数据(不考虑硬件情况,仅写出整个多任务框架模拟代码)。
FreeRTOS入门须知
学习一个 RTOS,搞懂它的编程的风格很重要,这可以大大提供我们阅读代码的效率。 下面我们就以 FreeRTOS里面的数据类型、变量名、函数名和宏这几个方面做简单介绍
1.数据类型
在 FreeRTOS 中,使用的数据类型虽然都是标准 C 里面的数据类型,但是针对不同的 处理器,对标准
C的数据类型又进行了重定义,给它们取了一个新的名字,比如 char 重新 定义了一个名字 portCHAR,这里面的
port表示接口的意思,就是 FreeRTOS要移植到这些 处理器上需要这些接口文件来把它们连接在一起。
需要注意:
在编程的时候,如果用户没有明确指定 char 的符号类型,那么编译器会默认的指定 char 型的变量为无符号或者有符号。正是因为这个原因,在 FreeRTOS 中,我们都需要明 确的指定变量 char是有符号的还是无符号的。在 keil 中,默认 char是无符号的,但是也可以配置为有符号的
2. 变量名
在 FreeRTOS 中,定义变量的时候往往会把变量的类型当作前缀加在变量上,这样的 好处是让用户一看到这个变量就知道该变量的类型。比如 char 型变量的前缀是 c,short 型 变量的前缀是 s,long型变量的前缀是 l, portBASE_TYPE类型变量的前缀是 x。还有其他 的数据类型,比如数据结构,任务句柄,队列句柄等定义的变量名的前缀也是 x。
如果一个变量是无符号型的那么会有一个前缀 u,如果是一个指针变量则会有一个前缀 p。
因此,当我们定义一个无符号的 char 型变量的时候会加一个 uc 前缀,当定义一个char型的指针变量的时候会有一个 pc前缀。
3. 函数名
函数名包含了函数返回值的类型、函数所在的文件名和函数的功能,如果是私有的函 数则会加一个
prv(private)的前缀。特别的,在函数名中加入了函数所在的文件名,这大大的帮助了用户提高寻找函数定义的效率和了解函数作用的目的,具体的举例如下:
- vTaskPrioritySet()函数的返回值为 void型,在 task.c这个文件中定义。
- xQueueReceive()函数的返回值为 portBASE_TYPE型,在 queue.c这个文件中定义。
- vSemaphoreCreateBinary()函数的返回值为 void型,在 semphr.h这个文件中定义。
4. 宏
宏均是由大写字母表示,并配有小写字母的前缀,前缀用于表示该宏在哪个头文件定义
裸机系统和多任务系统
什么叫裸机系统
裸机系统通常分成轮询系统和前后台系统
轮询系统:
即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情。
轮询系统是一种非常简单的软件结构,通常只适用于那些只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情。如果只是实现 LED 翻转,串口输出,液晶显示等
这些操作,那么使用轮询系统将会非常完美。但是,如果加入了按键操作等需要检测外部信号的事件,用来模拟紧急报警,那么整个系统的实时响应能力就不会那么好了。足见,轮询系统只适合顺序执行的功能代码,当有外部事件驱动时,实时性就会降低。
前后台系统:
相比轮询系统,前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里我们称为前台,main函数里面的无限循环我们称为后台。
由于中断的加入前后台系统确保了事件不会丢失,再加上中断具有可嵌套的功能,这可以大大的提高程序的实时响应能力。在大多数的中小型项目中,前后台系统运用的好,堪称有操作系统的效果。
多任务系统
相比前后台系统,多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。在多任务系统中,任务跟中断一样,也具有优先级,优先级高的任务会被优先执行。
当一个紧急的事件在中断被标记之后,如果事件对应的任务的优先级足够高, 就会立马得到响应。相比前后台系统,多任务系统的实时性又被提高了。
相比前后台系统中后台顺序执行的程序主体,在多任务系统中,根据程序的功能,我们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为任务。
每个任务都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。
加入操作系统后,我们在编程的时候不需要精心地去设计程序的执行流,不用担心每个功能模块之间是否存在干扰。
不同系统的总结
无论是裸机系统中的轮询系统、前后台系统和多任务系统,我们不能一锤子的敲定孰 优孰劣,它们是不同时代的产物,在各自的领域都还有相当大的应用价值,只有合适才是最好。
FreeRTOS的移植
了解了以上的基础知识,对FreeRTOS有了更深的了解,现在我们可以开始进行FreeRTOS的移植了。
1.资源的下载
由于我使用的是野火的板子,依照参考书的建议还是选择 V9.0.0,因为内核很稳定, 并且网上资料很多。
下载好后解压,Demo文件夹里面是例程,FreeRTOS 文件夹下的 Source 文件夹里面包含的是 FreeRTOS 内 核的源代码,我们移植 FreeRTOS 的时候就需要这部分源代码
编号①和 ③包含的是 FreeRTOS 的通用的头文件和 C 文件,这两部分的文件试用于各种编译器和处理器,是通用的。需要移植的头文件和C文件放在编号②portblle这个文件夹。
我们打开 portblle 这个文件夹,可以看到里面很多与编译器相关的文件夹,在不同的 编译器中使用不同的支持文件。
Keil文件夹中的就是我们就是我们使用的编译器,当你打开 KEIL文件夹的时候,你会看到一句话“See-also-the-RVDS-directory.txt”,其实 KEIL 里面的内容跟 RVDS 里面的内容一样,所以我们只需要RVDS 文件夹里面的内容即可。而MemMang 文件夹下存放的是跟内存管理相关的
打开 RVDS 文件夹,下面包含了各种处理器相关的文件夹,从文件夹的名字我们就非 常熟悉了,我们学习的 STM32 有 M0、M3、M4等各种系列,FreeRTOS是一个软件,单片机是一个硬件,FreeRTOS要想运行在一个单片机上面,它们就必须关联在一起,那么怎么关联?
还是得通过写代码来关联,这部分关联的文件叫接口文件,通常由汇编和C 联合 编写。这些接口文件都是跟硬件密切相关的,不同的硬件接口文件是不一样的,但都大同小异。
编写这些接口文件的过程我们就叫移植,移植的过程通常由 FreeRTOS 和 mcu 原厂的人来负责,移植好的这些接口文件就放在RVDS这个文件夹的目录下
以 ARM_CM3这个文件夹为例,看看里面的文件,里面只有“port.c”与“portmacro.h” 两个文件,
port.c 文件里面的内容是由 FreeRTOS 官方的技术人员为 Cortex-M3内核的处理
器写的接口文件,里面核心的上下文切换代码是由汇编语言编写而成,对技术员的要求比
较高,我们刚开始学习的之后只需拷贝过来用即可,深入的学习可以放在后面的日子;portmacro.h 则是 port.c 文件对应的头文件,主要是一些数据类型和宏定义
MemMang 文件夹下存放的是跟内存管理相关的,总共有五个 heap 文件以及一 个 readme说明文件
由于现在是初学,所以我们选用 heap4.c即可
2.往裸机工程添加 FreeRTOS源码
在根目录下添加新的文件夹FreeRTOS
FreeRTOS文件夹下创建新的子文件夹port,src,include文件夹可以直接整个复制
1.上文FreeRTOS解压后的source文件夹中所有.c文件复制到src文件夹中
2.“MemMang”文件夹与“RVDS”文件夹,将它们拷贝到我们新建的 port 文件夹中
3.在“FreeRTOSv9.0.0\ FreeRTOS\Source”目录下找到 “include”文件夹,它是我们需要用到 FreeRTOS 的一些头文件,将它直接拷贝到我们新建的 FreeRTOS 文件夹中
至此,FreeRTOS的源码就提取完成
3.添加 FreeRTOS源码到工程组文件夹
鉴于 FreeRTOS 容量很小,我们直接将刚刚提取的整个 FreeRTOS 文件夹拷贝到我们 的 STM32裸机工程里面,让整个 FreeRTOS跟随我们的工程一起发布,使用这种方法打包 的 FreeRTOS 工程,即使是将工程拷贝到一台没有安装 FreeRTOS支持包(MDK 中有 FreeRTOS 的支持包)的电脑上面都是可以直接使用的,因为工程已经包含了 FreeRTOS 的源码。
1.打开 FreeRTOSv9.0.0 源码,在“FreeRTOSv9.0.0\FreeRTOS\Demo”文件夹下面找到 “CORTEX_STM32F103_Keil ”这个文件夹,双击打开,在其根目录下找到这个
“FreeRTOSConfig.h”文件,然后拷贝到我们工程的 user 文件夹下
2.接下来我们在开发环境里面新建 FreeRTOS/src 和 FreeRTOS/port 两个组文件夹, 其中 FreeRTOS/src 用于存放 src 文件夹的内容, FreeRTOS/port 用于存放 port\MemMang 文件夹 与
port\RVDS\ARM_CM?文件夹的内容,“?”表示 3、4 或者 7
本人使用的是野火的MINI开发板,需要串口文件为ARM_CM3
4.指定 FreeRTOS头文件的路径
FreeRTOS的源码已经添加到开发环境的组文件夹下面,编译的时候需要为这些源文件 指定头文件的路径,不然编译会报错。
把ARM_CM3和include加入Path里面,user也要加进去,因为FreeRTOSConfig.h在这个文件夹
5.FreeRTOSConfig.h文件修改
这个步骤太过麻烦建议参照野火的《FreeRTOS内核实现与应用开发实战.pdf》跟着做
这里就不再赘述
6.修改 stm32f10x_it.c
同上
7.修改main.c
延时500ms闪烁一次LED
static void LED_Task(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500);
LED1_OFF;
vTaskDelay(500);
}
}
向串口发送helloworld!
```c
static void Usartmessage_Task(void* parameter)
{
while(1)
{
vTaskDelay(5000);
printf("helloworld!\r\n");
}
}
管理函数:
```c
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建LED_Task任务 */
xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
(const char* )"LED_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建LED_Task任务成功!\r\n");
/*创建串口发送helloworld任务*/
xTaskCreate((TaskFunction_t )Usartmessage_Task, /* 任务入口函数 */
(const char* )"Usartmessage_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建Usartmessage_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
初始化函数
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
}
现在让我们来看看烧录后的效果吧!
打开串口调试器
烧录成功后能看到创建任务成功的提示,之后每隔5000ms会收到串口发来的helloworld(本来应该是2000ms,这里是我设置错数值了,问题不大)
LED灯闪烁情况(本人录制视频)
AHT20的伪代码如下:
根据AH20产品手册可以得到换算关系式
static void AHT20_Task(void* parameter)
{
int T,RH;
while(1)
{
vTaskDelay(5000);
T=(getST(AH20)/pow(2,20))*200-50;
RH=(getSRH(AH20)/pow(2,20));
printf("温度为:“+T+”\r\n");
printf("湿度为:“+RH+”\r\n");
}
}