【RP2物联网实战(一)】C/C++&FreeRTOS版

写在前面

  树莓派pico,又称为RP2,其开发方式多种多样,有C语言和micropython语言,工具有cmake,vscode,arduino,thonny等,本版主要使用的工具链是VsCode结合cmake、官方提供的SDK——pico-sdk和移植的FreeRTOS。环境搭建见这四章:交叉编译踩坑指北
  现在默认你已经完成了环境的搭建,让我们来概括性的认识一下我们所搭建的工具链。

1、PICO-SDK

  首先是树莓派pico官方提供的pico-sdk。参考资料为github pico-example和pico-sdk的C开发手册。pico-sdk是树莓派官方给RP2提供的一个API,主要包含了GPIO,ADC,TIMER,INTERRUPT等函数封装,并且提供了丰富的例子,甚至包含了诸如DHT11,7段数码管的简单驱动。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第1张图片

1.1 PICO-SDK的CMake组织方式

在这里插入图片描述

  如pico-sdk文档中的话所示, 其官方文件夹是用Cmake组织链接起来的。那么,pico-sdk是如何被使用的呢?我们可以在CMakeLists中一窥究竟。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第2张图片

  在最外层的CMake文件中,我们可以看到它使用include指令,将pico_sdk_import.cmake文件加入进来(该文件是pico-sdk官方提供的)。这是cmake链接的一种方式,接下来就可以具体看到它是如何把我们的项目工程和pico-sdk链接起来的。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第3张图片

  可以看到,在这里有一个变量PICO_SDK_PATH,指定的就是你存放官方提供的pico-sdk的位置。你在编译时指定或者在CMakeLists中指定该变量的值就可以了。这样就可以让CMake在顶层的CMakeLists的指引下找到pico-sdk这个文件夹。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第4张图片
  而这个文件夹也是官方直接提供的,可以看到这个文件夹也有其顶层的CMakeLists,CMakeLists就像一个指引器,指引CMake进入到各种子文件夹中,而子文件夹中也有其各自的CMakeLists,向一棵树一样发散开去。但是因为这个pico-sdk文件夹是官方提供的,这个文件夹内部是怎么编写和放置CMakeLists来指引CMake的我们不关心,已经被官方的人做好了。我们要做的就是把我们工程的CMake指向这个文件夹就好了,之后的工作已经被封装好了。

1.2 PICO-SDK的使用方式

  那么在代码中我们该如何调用pico-sdk呢?首先我们需要知道pico-sdk提供了许多库。包括了高层的库和底层的库,这些都在pico-sdk官方文档中说的很详细。当你需要使用哪个库时,只需要查询官方文档,将其名字以库的形式添加在指导main.c文件编译的CMakeLists中即可。具体而言如下所示,比方说要使用adc功能,在官方文档中查找到库名字的声明,这是第四节,在hardware分类下。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第5张图片

  所以我们在直接编译main.c文件的CMakeLists中找到链接库的语句,加入hardware_adc即可
【RP2物联网实战(一)】C/C++&FreeRTOS版_第6张图片
  在main.c等文件中应用时,遵循其分类,因为再硬件库分类下,使用hardware/adc.h来表示,这一块可以参考文档中给出的示例就可以,如下所示。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第7张图片
  因为pico-sdk提供的库已经内部链接了头文件,因此我们无需在CMake中声明include路径,直接在main.c文件里使用就行了。(下一节FreeRTOS编译的过程中将会看到这种为库内部链接头文件是怎么用CMake实现的)。
此外还有一个常用的主库pico/stdlib,这个库包含了许多常用子库如时间库,gpio库等等。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第8张图片

1.3 PICO-SDK常见函数的简单介绍

  因为文档比较长,秉承着用到哪学哪的理念,我使用较多的为gpio、time还有adc功能。

函数 功能
gpio_init(PIN) 初始化管脚
gpio_set_dir(PIN,GPIO_OUT/GPIO_IN) 设置gpio方向
gpio_put(PIN,1/0) 输出高/低电平
x=gpio_get(PIN) 获取引脚电平
adc_init() 初始化ADC
adc_set_temp_sensor_enabled(true) 开启内置温度计
adc_select_input(channel) 选择ADC通道,4通道是温度计
gpio_set_mask(mask) gpio数据掩码赋值
gpio_clr_mask(mask) gpio清除掩码

2、 FreeRTOS

  前面介绍了Pico-SDK,实际上它就相当于一个库的集合,一个包,被链接到我们的工程文件中来。这里的FreeRTOS其实也是一样的,也是通过CMake链接进来。只不过我们下载的是FreeRTOS的源代码,需要先用CMake把它转换成库,然后链接进来。

2.1 FreeRTOS的CMake组织方式

  如前所述,既然pico-sdk已经是CMake所组织连接的,因此我们在已知FreeRTOS时也要使用CMake。
  首先去FreeRTOS官网下载V9.0.0版本,虽然不是up-to-dated,但是是比较主流的。我们主要用到的文件在FreeRTOSv9.0.0\FreeRTOS\Source文件夹下,当然,官方也给出了很多移植的demo,在FreeRTOSv9.0.0\FreeRTOS\Demo下有许多芯片型号的移植实例。
  我们先新建一个文件夹freertos,其中包括了FreeRTOSKernel,CMakeLists和FreeRTOSConfig.h这几个文件,如下图所示。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第9张图片

  其中,FreeRTOS-Kernel文件夹中主要存放的是之前所说的FreeRTOS Source文件夹提供的源文件。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第10张图片
  我们可以在此基础上对不需要的功能进行剪裁,或者可以在FreeRTOS Plus文件中加入额外的功能比如UDP通讯等等。总之就是根据需要对源代码进行相应的取舍。但是在这里不删减也没关系,因为后面讲到的配置文件会利用条件编译的方法来决定是否加入某种功能。
  CMakeLists的内容如下,
【RP2物联网实战(一)】C/C++&FreeRTOS版_第11张图片

#提取文件夹中的源文件生成静态库freertos.a在build/freertos下
#将头文件添加到目标头文件路径中,可能是为了辅助生成freertos库
set(PICO_SDK_FREERTOS_SOURCE FreeRTOS-Kernel)#添加文件夹位置变量

add_library(freertos#生成库
    ${PICO_SDK_FREERTOS_SOURCE}/event_groups.c#事件功能
    ${PICO_SDK_FREERTOS_SOURCE}/list.c
    ${PICO_SDK_FREERTOS_SOURCE}/queue.c#队列功能(队列消息,信号量,互斥量)
    ${PICO_SDK_FREERTOS_SOURCE}/stream_buffer.c
    ${PICO_SDK_FREERTOS_SOURCE}/tasks.c#任务功能
    ${PICO_SDK_FREERTOS_SOURCE}/timers.c#定时器功能
    ${PICO_SDK_FREERTOS_SOURCE}/portable/MemMang/heap_3.c#内存管理模块
    ${PICO_SDK_FREERTOS_SOURCE}/portable/GCC/ARM_CM0/port.c
)

target_include_directories(freertos PUBLIC#为库连接头文件
    .
    ${PICO_SDK_FREERTOS_SOURCE}/include
    ${PICO_SDK_FREERTOS_SOURCE}/portable/GCC/ARM_CM0
)

  add_library()是CMake中生成库的指令.默认是静态库(.a),生成库 add_library(libname [SHARED| STATIC] source1 …sourceN) 指令用于将一系列源文件指定身成为静态库或者动态库。例如本例中为静态库,就会在指定的libname前面添加lib作为生成库文件的名字,后缀为.a
【RP2物联网实战(一)】C/C++&FreeRTOS版_第12张图片
  将头文件位置加入了freertos的include搜索路径,使用指令**target_include_directories**,cmake引入头文件有2条指令分别是 include_directories()target_include_directories() 区别是,前者是当前CMakeLists.txt中所有目标的搜索路径及其之后子目录目标的搜索路径后者是指定目标所包含的头文件路径(一般是先生成文件,才能指定,因此是出现在后面的)后者的好处是,具体化,对于不同文件夹下相同名称的文件也具有区分性

  target_include_directories( [SYSTEM] [AFTER|BEFORE] [items1…])
  其中指定的参数代表含义如下
  INTERFACE:target对应的对头文件使用
  PRIVATE:target对应的源文件使用
  PUBLIC:target对应的头文件/源文件都可以用

  以源文件构建库文件之一的源文件list.c为例,其中就有include
【RP2物联网实战(一)】C/C++&FreeRTOS版_第13张图片
  因此使用target_include_directories可以为库中的每个文件引入头文件的路径进行具体化,使其可以找到特定的头文件而不会出错。

  FreeRTOSConig.h头文件在FreeRTOS.h中被引用,如下图所示
【RP2物联网实战(一)】C/C++&FreeRTOS版_第14张图片

  当我们在主程序中调用FreeRTOS核心库FreeRTOS.h时,会自动调用配置文件FreeRTOSConfig.h文件。下面简单讲一下该文件中的一些常用功能与配置。
【RP2物联网实战(一)】C/C++&FreeRTOS版_第15张图片
  其实在这本书中除了对FreeRTOS有详细讲解外,也穿插着对其配置文件有阐述。
  例如
【RP2物联网实战(一)】C/C++&FreeRTOS版_第16张图片

#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/* Use Pico SDK ISR handlers */
#define vPortSVCHandler         isr_svcall
#define xPortPendSVHandler      isr_pendsv
#define xPortSysTickHandler     isr_systick

#define configUSE_PREEMPTION                    1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0//使能它时任务优先级可以支持32级
#define configUSE_TICKLESS_IDLE                 0//低功耗使能,置0系统节拍中断一直运行
#define configCPU_CLOCK_HZ                      133000000//设置CPU频率133M,用于在port.c文件中进行时钟配置
#define configTICK_RATE_HZ                      1000//设置FREERTOS系统时钟节拍频率HZ,系统滴答定时器的中断频率,设为1000则系统时钟节拍周期1ms,软件定时器的值必须是该周期的整数倍;pdMS_TO_TICKS()可以将毫秒转换为tick,前提是该宏小于1000
#define configMAX_PRIORITIES                    5//设置任务优先级数量从0-该数-1,就绪列表数组大小,最大支持255,数值越大优先级越高
#define configMINIMAL_STACK_SIZE                128//设置空闲任务的最小任务堆栈大小(字)
#define configMAX_TASK_NAME_LEN                 16//设置任务名最大长度
#define configUSE_16_BIT_TICKS                  0//1:8位存储事件组  0:24位存储事件组,*推荐:0
#define configIDLE_SHOULD_YIELD                 1//空闲任务与处于同等优先级的其他用户任务的行为.为1时空闲任务会为同等优先级的用户任务让出cpu使用权
#define configUSE_TASK_NOTIFICATIONS            1
#define configTASK_NOTIFICATION_ARRAY_ENTRIES   3
#define configUSE_MUTEXES                       1//使用互斥量
#define configUSE_RECURSIVE_MUTEXES             1//使用递归互斥量
#define configUSE_COUNTING_SEMAPHORES           1//使用计数信号量
#define configQUEUE_REGISTRY_SIZE               10//设置可以注册的信号量和消息队列个数
#define configUSE_QUEUE_SETS                    0//启用队列集
#define configUSE_TIME_SLICING                  1//使能时,处于就绪态的多个相同优先级任务会以时间分片方式共享CPU
#define configUSE_NEWLIB_REENTRANT              0
#define configENABLE_BACKWARD_COMPATIBILITY     0//支持RTOS8.0之前的数据类型
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
#define configSTACK_DEPTH_TYPE                  uint16_t
#define configMESSAGE_BUFFER_LENGTH_TYPE        size_t

/* Memory allocation related definitions. */
#define configSUPPORT_STATIC_ALLOCATION         0//SRAM静态分配任务栈和任务控制块的内存,需要用户自定义两个函数设定空任务和定时器任务的堆栈大小,建议使用动态
#define configSUPPORT_DYNAMIC_ALLOCATION        1//SRAM动态分配任务栈和任务控制块的内存,分配到堆。堆在heap_x.c文件中定义.支持动态分配任务、队列、信号等
#define configAPPLICATION_ALLOCATED_HEAP        1//配置为用户自行设置堆内存而不是编译器自动分配,设置configTOTAL_HEAP_SIZE
#define configTOTAL_HEAP_SIZE                   ((size_t)(36*1024))//定义堆内存大小
/* Hook function related definitions. */
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0
#define configCHECK_FOR_STACK_OVERFLOW          0//检测任务栈溢出
#define configUSE_MALLOC_FAILED_HOOK            0
#define configUSE_DAEMON_TASK_STARTUP_HOOK      0

/* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS           0//开启时间统计功能,需要再定义portCONFIGURE_TIMER_FOR_RUN_TIME_STATUS()等宏
#define configUSE_TRACE_FACILITY                0
#define configUSE_STATS_FORMATTING_FUNCTIONS    0

/* Co-routine related definitions. */
#define configUSE_CO_ROUTINES                   0//为1时可以启动协程
#define configMAX_CO_ROUTINE_PRIORITIES         1//协程的最大优先级数,协程优先级可以从0到该值-1

/* Software timer related definitions. */
#define configUSE_TIMERS                        1//使能了定时器,会自动创建定时器任务,ISR中设置事件需要,软件定时器需要
#define configTIMER_TASK_PRIORITY               4//软件定时器 任务 优先级,应当设置为最高(原来是3),守护进程的优先级
#define configTIMER_QUEUE_LENGTH                10//软件定时器命令队列长度,定时器通信不能直接访问,被API封装
#define configTIMER_TASK_STACK_DEPTH            configMINIMAL_STACK_SIZE//定时器任务堆栈大小128

/* Define to trap errors during development. */
#define configASSERT( x )//定义断言函数,具体内容需要用户自定义

/* Optional functions - most linkers will remove unused functions anyway. */
//使能或这解除使能对应的API函数
#define INCLUDE_vTaskPrioritySet                1
#define INCLUDE_uxTaskPriorityGet               1
#define INCLUDE_vTaskDelete                     1//启用任务删除函数
#define INCLUDE_vTaskSuspend                    1//启用任务挂起功能函数和任务恢复函数vTaskSuspend()和vTaskResume,会使得信号和消息的阻塞等待时间可以为无穷大portMAX_Delay
#define INCLUDE_xResumeFromISR                  1
#define INCLUDE_vTaskDelayUntil                 1//启用绝对阻塞延时函数
#define INCLUDE_vTaskDelay                      1//启用相对阻塞延时函数
#define INCLUDE_xTaskGetSchedulerState          1
#define INCLUDE_xTaskGetCurrentTaskHandle       1
#define INCLUDE_uxTaskGetStackHighWaterMark     0
#define INCLUDE_xTaskGetIdleTaskHandle          0
#define INCLUDE_eTaskGetState                   0
#define INCLUDE_xEventGroupSetBitFromISR        1
#define INCLUDE_xTimerPendFunctionCall          1//原来是0;支持xEventGroupSetBitsFromISR()事件组中断置位函数
#define INCLUDE_xTaskAbortDelay                 0
#define INCLUDE_xTaskGetHandle                  0
#define INCLUDE_xTaskResumeFromISR              1//在中断中恢复挂起任务

/* A header file that defines trace macro can be included here. */

#endif /* FREERTOS_CONFIG_H */

3、FreeRTOS功能介绍(部分)

3.1任务管理Task

  从系统的角度看,任务是竞争系统资源的最小运行单元。FreeRTOS 是一个支持多任务的操作系统。在FreeRTOS 中,任务可以使用或等待CPU、使用内存空间等系统资源,并独立于其它任务运行, 任何数量的任务可以共享同一个优先级, 如果宏configUSE_TIME_SLICING 定义为1,处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。
  FreeRTOS 的任务可认为是一系列独立任务的集合。每个任务在自己的环境中运行。在任何时刻,只有一个任务得到运行,FreeRTOS 调度器决定运行哪个任务。调度器会不断的启动、停止每一个任务,宏观看上去所有的任务都在同时在执行。作为任务,不需要对调度器的活动有所了解,在任务切入切出时保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责
  FreeRTOS 中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。,当有比当前任务优先级更高的任务就绪时,当前任务将立刻被换出,高优先级任务抢占处理器运行。同时FreeRTOS 也支持时间片轮转调度方式。
  FreeRTOS的任务状态迁移如下图所示

【RP2物联网实战(一)】C/C++&FreeRTOS版_第17张图片
  挂起和解除挂起任务的函数分别是vTaskSuspend()和vTaskResume(),挂起态任务对任务调度器是不可见的,而阻塞情况比较多,例如vTaskDelay()延时或者等待信号量、资源占用结束、事件发生等。
  xTaskCreate用于创建任务,参数指明任务句柄、优先级、任务名等信息。
  vTaskDelete同于删除任务,vTaskStartScheduler()打开任务调度器,FreeRTOS正式接管。
  空闲任务(idle 任务) 是FreeRTOS 系统中没有其他工作进行时自动进入的系统任务。因为处理器总是需要代码来执行——所以至少要有一个任务处于运行态。FreeRTOS 为了保证这一点,当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务,空闲任务是一个非常短小的循环。用户可以通过空闲任务钩子方式,在空闲任务上钩入自己的功能函数。通常这个空闲任务钩子能够完成一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等
  

3.2消息队列Queue

【RP2物联网实战(一)】C/C++&FreeRTOS版_第18张图片

  消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息。消息队列是一种异步的通信方式。(可能类似python多进程中的管道)
  一个任务可以向任何一个队列发送消息,也可以从任何一个队列接收消息。默认是按照FIFO的方式发送和读取,也可以配置为LIFO。
xQueueCreate():消息队列创建函数
vQueueDelete():消息队列删除函数
xQueueSend():向消息队列发送函数
xQueueSendToFront():向消息队列发送紧急消息函数
xQueueReceive():从消息队列读取消息函数
xQueuePeek():从消息队列读取消息函数(不删除消息)

3.3 信号量Semaphore

  基于队列,只不过不关心消息内容,只关心消息的个数。有二值、计数、互斥信号量等。
xSemaphoreCreateBinary()创建二值信号
xSemaphoreCreateCounting()创建计数信号
vSemaphoreDelete()信号量删除函数
xSemaphoreTake() 信号量获取
xSemaphoreGive() 信号量释放

3.4互斥量Mutex

  特殊的信号量,最主要的区别是锁的概念。一个信号只能在同一个任务中进行申请和释放。而普通信号量可以在一个任务中申请而在另一个任务中进行释放。
xSemaphoreCreateMutex()互斥信号量创建
vSemaphoreDelete()(互斥)信号量删除
xSemaphoreTake() (互斥)信号量获取
xSemaphoreGive() (互斥)信号量释放

3.5事件Event

【RP2物联网实战(一)】C/C++&FreeRTOS版_第19张图片

  事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
  如果宏configUSE_16_BIT_TICKS 定义为1,那么变量uxEventBits 就是16 位的,其中有8 个位用来存储事件组;而如果宏configUSE_16_BIT_TICKS 定义为0,那么变量uxEventBits 就是32 位的, 其中有24 个位用来存储事件组。
xEventGroupCreate()事件创建函数
vEventGroupDelete()事件删除函数
xEventGroupSetBits()事件组置位函数
xEventGroupWaitBits()等待事件函数
xEventGroupClearBits()事件组清零位函数

3.6软件定时器timer

【RP2物联网实战(一)】C/C++&FreeRTOS版_第20张图片

  定时器有硬件定时器和软件定时器之分:
  硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
  软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中处理信息;而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(也称超时函数/回调函数,为了统一,下文均用回调函数描述),在回调函数中处理信息。
  单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。
  周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除
  通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为configTICK_RATE_HZ,该宏在FreeRTOSConfig.h 中有定义,默认是1000。那么系统的时钟节拍周期就为1ms(1s 跳动1000 下,每一下就为1ms)。软件定时器的所定时数值必须是这个节拍周期的整数倍,
xTimerCreate()软件定时器创建函数
xTimerStart()软件定时器启动函数
xTimerStop()软件定时器停止函数
xTimerDelete()软件定时器删除函数

任务通知,内存管理,中断:暂时未看

4、实例测温展示

  硬件RP2-H,DS18B20,4共阴数码管


main.c
//VERSION 1.0.1 TIME 
//加入DS18B20传感器测试模块
#include 
#include 
#include 
#include 
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "DS18B20.h"
#define FIRST_GPIO 2
#define QUEUE_LEN 4//队列的长度,最大包含多少消息
#define QUEUE_SIZE 4//队列中每个消息的字节
int Temperature_temp=0;
int bits[10] = {//共阴极7段数码管
        0x3f,  // 0
        0x06,  // 1
        0x5b,  // 2
        0x4f,  // 3
        0x66,  // 4
        0x6d,  // 5
        0x7d,  // 6
        0x07,  // 7
        0x7f,  // 8
        0x6f   // 9
};
QueueHandle_t Test_Queue1=NULL;
void task1(void *pvParameters){
    while(true){
        const float conversion_factor=3.3f/(1<<12);
        float adc=(float)adc_read()*conversion_factor;
        float Temperature=27 - (adc - 0.706)/0.001721;
        int Temperature_temp=(int)(Temperature*100);

        printf("发送队列消息Temperature\n");
        xQueueSend(Test_Queue1,&Temperature_temp,0);

        printf("voltage: %f V\nOn board Temperature: %f Degree\n",adc,Temperature);   
        // We are starting with GPIO 2, our bitmap starts at bit 0 so shift to start at 2.
        vTaskDelay(2000);
    }
}
void task2(void *pvParameters){
    while(true){
        int a,b,c,d;
        xQueueReceive(Test_Queue1,&Temperature_temp,1);
        a=Temperature_temp/1000;
        b=Temperature_temp/100-a*10;
        c=Temperature_temp/10-a*100-b*10;
        d=Temperature_temp-a*1000-b*100-c*10;
        int32_t mask = bits[a] << FIRST_GPIO;//掩码
        // Set all our GPIOs in one go!
        // If something else is using GPIO, we might want to use gpio_put_masked()
        gpio_set_mask(mask);
        gpio_put(9,0);
        gpio_put(10,0);
        gpio_put(11,1);
        gpio_put(12,1);
        gpio_put(13,1);
        vTaskDelay(5);
        gpio_clr_mask(mask);  //清除上一次的值
        
        mask = bits[b] << FIRST_GPIO;//掩码
        gpio_set_mask(mask);
        gpio_put(9,1);
        gpio_put(10,1);
        gpio_put(11,0);
        gpio_put(12,1);
        gpio_put(13,1);
        vTaskDelay(5);
        gpio_clr_mask(mask);  //清除上一次的值 
        
        mask = bits[c] << FIRST_GPIO;//掩码
        gpio_set_mask(mask);
        gpio_put(9,0);
        gpio_put(10,1);
        gpio_put(11,1);
        gpio_put(12,0);
        gpio_put(13,1);
        vTaskDelay(5);
        gpio_clr_mask(mask);  //清除上一次的值     

        mask = bits[d] << FIRST_GPIO;//掩码
        gpio_set_mask(mask);
        gpio_put(9,0);
        gpio_put(10,1);
        gpio_put(11,1);
        gpio_put(12,1);
        gpio_put(13,0);
        vTaskDelay(5);
        gpio_clr_mask(mask);  //清除上一次的值 
    }

}
void task3(void *pvParameters){//DS18B20传感器测温任务
  while(true){
        ReadTemperature();
        vTaskDelay(2000);

  }
}
int main(){

    stdio_init_all();
    gpio_init(PICO_DEFAULT_LED_PIN);//初始化板载LED,表征进入主程序
    gpio_set_dir(PICO_DEFAULT_LED_PIN,GPIO_OUT);
    gpio_put(PICO_DEFAULT_LED_PIN,1);
    gpio_init(DQ_PIN);//初始化外设温度器数据引脚GPIO22
    gpio_set_dir(DQ_PIN,GPIO_OUT);

    //初始化8段数码管GPIO引脚
    for (int gpio = FIRST_GPIO; gpio < FIRST_GPIO + 12; gpio++) {
        gpio_init(gpio);
        gpio_set_dir(gpio, GPIO_OUT);
        // Our bitmap above has a bit set where we need an LED on, BUT, we are pulling low to light
        // so invert our output
        //gpio_set_outover(gpio, 1);
    }
     
    printf("ADC Meausring GPIO026\n");
    adc_init();
    adc_set_temp_sensor_enabled(true);
    adc_select_input(4);//temperature
    xTaskCreate(task1, "ADC_Task", 256, NULL, 1, NULL); 
    xTaskCreate(task2, "SEG_Task", 256, NULL, 1, NULL);
    xTaskCreate(task3,"DS18B20_Task",256,NULL,1,NULL);
    Test_Queue1=xQueueCreate(QUEUE_LEN,QUEUE_SIZE);  
    vTaskStartScheduler();

    while(true){};
}


DS18B20.c
#include "DS18B20.h"
#include "pico/stdlib.h"
#include 
/*****初始化DS18B20*****/
unsigned int Init_DS18B20(void)
{
    unsigned int x=0;
    // gpio_put(DQ_PIN,1);//DQ复位
    // sleep_us(100);    //稍做延时
    gpio_set_dir(DQ_PIN, GPIO_OUT);
    gpio_put(DQ_PIN,0);//单片机将DQ拉低
    sleep_us(500);   //精确延时,大于480us
    gpio_put(DQ_PIN,1);//拉高总线15us
    sleep_us(15);
    gpio_set_dir(DQ_PIN,GPIO_IN);  //释放总线
    sleep_us(45);
    x =gpio_get(DQ_PIN);//稍做延时后,如果x=0则初始化成功,x=1则初始化失败
    if(x==0){
        // printf("初始化成功\n");
    }
    else{
        // printf("初始化失败\n");
    }
    return x;
}
/*****写一个字节*****/
void WriteOneChar(unsigned char dat)//先写低字节
{
    unsigned char i=0;
    for (i=8; i>0; i--)
    {
        gpio_set_dir(DQ_PIN,GPIO_OUT); 
        gpio_put(DQ_PIN,0);//单片机将DQ拉低
        sleep_us(1);
        gpio_put(DQ_PIN,dat&0x01);//与1按位与运算,dat最低位为1时DQ总线为1,dat最低位为0时DQ总线为0
        dat>>=1;
        sleep_us(70);//采样时间
        gpio_put(DQ_PIN,1);
        gpio_set_dir(DQ_PIN,GPIO_IN);//释放总线  
        sleep_us(10);         
    }
}

/*****读一个字节*****/
unsigned char ReadOneChar(void)
{
  unsigned char i=0;
  unsigned char dat = 0;
  for (i=8;i>0;i--)
  {
    gpio_set_dir(DQ_PIN,GPIO_OUT); 
    gpio_put(DQ_PIN,0);// 给脉冲信号
    dat>>=1;
    sleep_us(2);
    gpio_put(DQ_PIN,1);// 给脉冲信号
    gpio_set_dir(DQ_PIN,GPIO_IN); //释放总线
    sleep_us(6);
    if(gpio_get(DQ_PIN))
    	dat|=0x80;
    sleep_us(48);
  }
  return(dat);
}

/*****读取温度*****/
int ReadTemperature(void)
{
  	unsigned char a=0;
  	unsigned char b=0;
  	unsigned int t=0;
    float t2=0;
  	Init_DS18B20();
    sleep_ms(1);
  	WriteOneChar(0xCC);  //跳过读序号列号的操作
  	WriteOneChar(0x44);  //启动温度转换
  	Init_DS18B20();
    sleep_ms(1);
  	WriteOneChar(0xCC);  //跳过读序号列号的操作
  	WriteOneChar(0xBE);  //读取温度寄存器
  	a=ReadOneChar();     //读低8位
  	b=ReadOneChar();     //读高8位
  	t=b;
  	t<<=8;
    // printf("t=b并左移8位的值:%d",t);
  	t=t|a;
    // printf("16位数据为%d\n",t);    
	t2=t*0.0625-4.2;//修正
    printf("DS18B20 Temperature: %fDegree\n",t2);
  	return(t);
}



DS18B20.h
#ifndef _DS18B20_H_
#define _DS18B20_H_
#define DQ_PIN 22
unsigned int Init_DS18B20(void);
void WriteOneChar(unsigned char dat);
unsigned char ReadOneChar(void);
int ReadTemperature(void);
#endif

CMakeLists.txt
add_executable(blink
        main.c DS18B20.c
)

target_link_libraries(blink pico_stdlib freertos hardware_adc)

target_include_directories(blink PUBLIC .)#增加的

pico_enable_stdio_usb(blink 1)#add ,parameter1 is the program
pico_enable_stdio_uart(blink 0)#add ,disable uart

pico_add_extra_outputs(blink)

最终效果如下

【RP2物联网实战(一)】C/C++&FreeRTOS版_第21张图片  

  

  
  Linux开发环境下工具链同样跑通

【RP2物联网实战(一)】C/C++&FreeRTOS版_第22张图片

你可能感兴趣的:(嵌入式系统,c++,物联网,c语言)