21976 字
110 分钟
RT-Thread开发指北

STM32裸机开发#

STM32基础库#

STM32HAL库#

STM32CubeMX#

STM32CubeMX 其实是依靠着java8的后端逻辑和对应的前端页面进行设置其对应的硬件资源,其需要在移植正确RT-Thread对应的资源之后才可以使用其资源,对应的板层资源的头文件一般会在CubeMX_Config\Inc下 对应的

移植和准备开发#

  • 参考RT-Thread的nano文档但是书写和使用写的是标准版

RT-Thread Studio#

ConEmu#

什么是嵌入式系统#

1、以应用为中心,以计算机技术为基础,能够根据客户需求(功能、成本、功耗大小、外观、体积大小、应用场景),软件硬件模块集合 2、其实将CPU、存储单元、定时系统、IO接口、外设接口、结合我们的芯片,编写程序,把它嵌入到一个可以稳定运行的硬件设备,形成微型的计算机系统。 3、操作系统:OS,可以把它看作以一种内置的程序用于管理计算机的硬件和应用层(用户)的交互、留有各种各样的接口。 4、常见的操作系统分为桌面操作系统windows、AOSP、IOS、开源操作系统。嵌入式操作系统:freeRTOS|RT-thread。 5、应用:智能手环 测心率 洗衣机 电动车仪表盘 自动售货机硬件 智能猫眼:图像处理。

学习什么东西#

C、C++语言 Python语言 单片机寄存器操作位 复用接口 串口复用GPIO RTOS 管理多任务 多线程 项目实战

什么是单片机#

MCU:微控制器、CPU、存储单元、定时系统、IO接口、外设接口。MCU:内核 ROM、RAM、电源口、下载接口、烧录接口、IO、外设接口:串口、定时器、计数器、看门狗、ADC、IIC、SPI、CAN等。 MPU:微型协处理器。 内核架构类型、x86、RISC-V Cortex-A系列 A7、A9、A53 手机、平板、车载 Cortex-M系列 M0、M3、M4 微控系列 传感器采集节点和消费电子领域 Cortex-R系列 R5 追求实时控制 刹车系统、转向控制 主控芯片介绍 STM32F103ZET6 ST 意法半导体 M 微控制器类型 32位处理能力 F103 主流基础型 Z 引脚数 144脚 E FLASH 512K T 封装类型 QFP 6 温度范围 -40到85°C RAM 64K 寄存器开发 操作不同寄存器的不同位来实现不同的功能 位操作 &|!^ 执行效率高、不方便移植开发 标准库开发 官方封装好的一整套外设库函数、直接调用就行 代码可读性强 方便开发 HAL库开发 图形化配置界面 直接点击配置 自动生成底层初始化代码 配置更加方便 但是出现问题不好调试 环境搭建

什么是BSP#

板级支持包,厂商提供的BSP,让这款芯片能在特定环境下运行下去 使用的是RT-Thread 需要STM32F103ZET6的通用源码然后制作对应的STM32F103ZET6的BSP

RT-Thread#

国产操作系统 使用调度切换 使用单核CPU 切换分段执行多任务多线程 通过任务调度器的快速切换 给人以多任务运行的错觉 RT-Thread 与其他很多 RTOS 如 FreeRTOS、uC/OS 的主要区别之一是,它不仅仅是一个实时内核,还具备丰富的中间层组件 比linux体积更小 代码量更少 非常适合单片机操作系统进行控制

GPIO#

通用输入/输出端口、io口根据专用功能可分为电源引脚、复位引脚、调试/烧录引脚、时钟(晶振)引脚。此外就是普通的IO口 普通的I\O可分为通用IO 输入/输出和复用成外设的外设功能。 芯片有大量有引脚如何管理 端口号(GPIOA-GPIOG)+引脚号固定的(0-15) 大多数MCU的引脚都不止一个功能、不同引脚内部结构不一样、拥有的功能也不一样、输入输出模式可控制。 输出模式一般包括:推挽、开漏、上拉、下拉。引脚为输出模式时,可以通过配置引脚输出的电平状态为高电平或低电平来控制连接的外围设备。 输入模式一般包括:浮空、上拉、下拉、模拟。引脚为输入模式时,可以读取引脚的电平状态,即高电平或低电平。

输入INPUT模式#

─ 输入浮空 VDD和VSS都断开,检测电平由外部决定 ─ 输入上拉 VDD闭合 ,默认检测高电平 ─ 输入下拉 VSS闭合,默认检测低电平 ─ 模拟输入 专门处理模拟信号,TTL肖特基触发器进行滤波整形、交由ADC转换 通用输入输出(GPIO)和复用功能(AFIO)之间的区别在于功能的直接控制和功能模块的托管。 通用控制权直接交给内核 复用控制权交给片上外设 给片上外设进行处理

输出OUTPUT模式#

─ 通用开漏输出 只能输出低电平、想要高电平、需要外接上拉电平 ─ 通用推挽式输出 I²C需要 ─ 推挽式复用功能 ─ 开漏复用功能 使用API的方式获取引脚编号 使用 rt_pin_get() 获取引脚编号,如下获取 PF9 的引脚编号:

pin_number = rt_pin_get("PF.9");

设置引脚的高低电平 PIN_LOW 低电平,PIN_HIGH 高电平

void rt_pin_write(rt_base_t pin, rt_base_t value);

读取引脚电平

int rt_pin_read(rt_base_t pin);

使用宏定义的方式获取引脚编号 获取引脚号为 PF9 的 LED0 对应的引脚编号的示例代码如下所示:

#include <rtdevice.h>
#include <board.h> //如果不包含,可能会遇到错误提示没有'F'定义
#define LED0_PIN GET_PIN(F, 9)

获取到对应的引脚编号之后就是设置它对应的引脚模式 引脚工作模式可使用五种宏定义值如下 目前 RT-Thread 支持的引脚工作模式可取如所示的 5 种宏定义值之一,每种模式对应的芯片实际支持的模式需参考 PIN 设备驱动程序的具体实现:

#define PIN_MODE_OUTPUT 0x00 /* 输出 */
#define PIN_MODE_INPUT 0x01 /* 输入 */
#define PIN_MODE_INPUT_PULLUP 0x02 /* 上拉输入 */
#define PIN_MODE_INPUT_PULLDOWN 0x03 /* 下拉输入 */
#define PIN_MODE_OUTPUT_OD 0x04 /* 开漏输出 */

LED1->PE2 LED2->PE3 LED3->PE4 LED4->PE5 模块化编程:每一个模块都需要一个.c和.h .h进行预处理文件、宏定义、函数声明、变量声明、入口文件等等 .c里面放置变量定义、函数定义、需要的头文件进行主要业务代码逻辑的编写

蜂鸣器#

PN结 P->N NPN型三极管 需要给集电极一个高电平 有源BEEP才能导通 BEEP->PC0 模式还是通用推挽输出 流程与LED灯是一致的

按键#

按键的做用:切换工作模式、设置设备参数、如果设备资源紧张还需要用长、短按来实现不同的按键功能或者模式切换。 按键KEY1松开之后MCU检测到低电平 KEY2、KEY3、KEY4松开之后MCU检测到高电平 读取引脚电平

int rt_pin_read(rt_base_t pin);
//示例
#define BEEP_PIN_NUM 35 /* PB0 */
int status;
/* 蜂鸣器引脚为输出模式 */
rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
/* 设置低电平 */
rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
status = rt_pin_read(BEEP_PIN_NUM);

按键需要进行消抖 按键的抖动由按键的机械结构决定 进行消抖:软件消抖和硬件消抖加上十到二十秒。 长按短按的判定、设置一个阈值来区分是长按还是短按和按下的时间 练习:按键按一下 灯1亮 按两下灯2亮以此内推 按四下不亮。

时延 阻塞 进程#

延时操作 因为程序是轮询式顺序执行的所以想要确定延迟的时间只能通过程序的时间片和定时器来实现时间的精确控制 裸机:时间片变量加定时器。 操作系统:线程任务上下文切换。 线程是操作系统中调度器调度,进行任务上下文切换的最基本单元。 线程又可以叫它轻量级进程、一个进程直接可以包含多个线程,进程之间互不影响,有独立的空间;但是线程需要共享进程的空间、并且线程之间相互影响、进程之间相互独立。进程调度起来比线程方便很多。 优先级:重要的任务可设置相对较高的优先级,非重要的任务可以设置较低的优先级,不同的任务还可以设置相同的优先级,轮流运行。 RT-Thread存在两类线程、分别为用户线程和系统线程、系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除,每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的 CPU 使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了 CPU 的使用权。如果是中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复

线程的工作机制#

在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等。 线程栈 RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。 RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;对于 ARM Cortex-M 系列,普遍采用 32 个优先级。 空闲线程优先级最低

时间片#

每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick),详见《时钟管理》章节。假设有 2 个优先级相同的就绪态线程 A 与 B,A 线程的时间片设置为 10,B 线程的时间片设置为 5,那么当系统中不存在比 A 优先级高的就绪态线程时,系统会在 A、B 线程间来回切换执行,并且每次对 A 线程执行 10 个节拍的时长,对 B 线程执行 5 个节拍的时长。

线程的入口函数#

每个线程的栈大小不一样 其创建时候有对应的空间单位是字节、线程运行状态分为五种状态

状态描述
初始状态当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT
就绪状态在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY
运行状态线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING
挂起状态也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND
关闭状态当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE

无限循环模式:#

在实时系统中,线程通常是被动式的:这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务:

void thread_entry(void* paramenter)
{
while (1)
{
/* 等待事件的发生 */
/* 对事件进行服务、进行处理 */
}
}

顺序执行或有限次循环模式:#

如简单的顺序语句、do while() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。

static void thread_entry(void* parameter)
{
/* 处理事务 #1 */
…
/* 处理事务 #2 */
…
/* 处理事务 #3 */
}

注意:要延时,主动让出对应的线程

#define RT_EOK 0 /* 无错误 */
#define RT_ERROR 1 /* 普通错误 */
#define RT_ETIMEOUT 2 /* 超时错误 */
#define RT_EFULL 3 /* 资源已满 */
#define RT_EEMPTY 4 /* 无资源 */
#define RT_ENOMEM 5 /* 无内存 */
#define RT_ENOSYS 6 /* 系统不支持 */
#define RT_EBUSY 7 /* 系统忙 */
#define RT_EIO 8 /* IO 错误 */
#define RT_EINTR 9 /* 中断系统调用 */
#define RT_EINVAL 10 /* 非法参数 */

线程的管理方式#

创建、启动、运行、删除 创建和删除线程 一个线程要成为可执行的对象,就必须由操作系统的内核来为它创建一个线程。可以通过如下的接口

  • 创建一个动态线程:
rt_thread_t rt_thread_create(const char* name,
void (*entry)(void* parameter),
void* parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick);
参数描述
thread线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址
name线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉
entry线程入口函数
parameter线程入口函数参数
stack_start线程栈起始地址
stack_size线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐)
priority线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0 ~ 255,数值越小优先级越高,0 代表最高优先级
tick线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行
返回——
RT_EOK线程创建成功
-RT_ERROR线程创建失败
  • 启动线程 创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程初始化 / 创建成功后调用下面的函数接口让该线程进入就绪态:
rt_err_t rt_thread_startup(rt_thread_t thread);
  • 挂起和恢复线程 当线程调用 rt_thread_delay() 时,线程将主动挂起;当调用 rt_sem_take(),rt_mb_recv() 等函数时,资源不可使用也将导致线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其他线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。 线程挂起使用下面的函数接口:
rt_err_t rt_thread_suspend (rt_thread_t thread);

[!Note]

注:一个线程尝试挂起另一个线程是一个非常危险的行为,因此RT-Thread对此函数有严格的使用限制:该函数只能使用来挂起当前线程(即自己挂起自己),不可以在线程A中尝试挂起线程B。而且在挂起线程自己后,需要立刻调用 rt_schedule() 函数进行手动的线程上下文切换。这是因为A线程在尝试挂起B线程时,A线程并不清楚B线程正在运行什么程序,一旦B线程正在使用例如互斥量、信号量等影响、阻塞其他线程(如C线程)的内核对象,如果此时其他线程也在等待这个内核对象,那么A线程尝试挂起B线程的操作将会引发其他线程(如C线程)的饥饿,严重危及系统的实时性。

  • 恢复线程 就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。线程恢复使用下面的函数接口:
rt_err_t rt_thread_resume (rt_thread_t thread);

初始化和脱离线程#

线程的初始化可以使用下面的函数接口完成,来初始化静态线程对象:

rt_err_t rt_thread_init(struct rt_thread* thread,
const char* name,
void (*entry)(void* parameter), void* parameter,
void* stack_start, rt_uint32_t stack_size,
rt_uint8_t priority, rt_uint32_t tick);复制错误复制成功

静态线程的线程句柄(或者说线程控制块指针)、线程栈由用户提供。静态线程是指线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、被分配处理,内核不负责动态分配内存空间。需要注意的是,用户提供的栈首地址需做系统对齐(例如 ARM 上需要做 4 字节对齐)。线程初始化接口 rt_thread_init() 的参数和返回值见下表:

参数描述
thread线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址
name线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉
entry线程入口函数
parameter线程入口函数参数
stack_start线程栈起始地址
stack_size线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐)
priority线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0 ~ 255,数值越小优先级越高,0 代表最高优先级
tick线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行
返回——
RT_EOK线程创建成功
-RT_ERROR线程创建失败

对于用 rt_thread_init() 初始化的线程,使用 rt_thread_detach() 将使线程对象在线程队列和内核对象管理器中被脱离。线程脱离函数如下:

rt_err_t rt_thread_detach (rt_thread_t thread);复制错误复制成功

线程脱离接口 rt_thread_detach() 的参数和返回值见下表:

参数描述
thread线程句柄,它应该是由 rt_thread_init 进行初始化的线程句柄。
返回——
RT_EOK线程脱离成功
-RT_ERROR线程脱离失败

这个函数接口是和 rt_thread_delete() 函数相对应的, rt_thread_delete() 函数操作的对象是 rt_thread_create() 创建的句柄,而 rt_thread_detach() 函数操作的对象是使用 rt_thread_init() 函数初始化的线程控制块。同样,线程本身不应调用这个接口脱离线程本身。

控制线程#

当需要对线程进行一些其他控制时,例如动态更改线程的优先级,可以调用如下函数接口:

rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg);复制错误复制成功

线程控制接口 rt_thread_control() 的参数和返回值见下表:

函数参数描述
thread线程句柄
cmd指示控制命令
arg控制参数
返回——
RT_EOK控制执行正确
-RT_ERROR失败

HOOK解释#

1、 Hook 的工作原理简析 一个典型的Hook机制包含三个部分: 钩子点:主程序中预定义好的位置(事件点),例如“启动后”、“渲染前”。 钩子函数:由外部开发者编写的、符合特定接口的函数。 注册/回调机制:外部函数向主程序“注册”或“订阅”某个钩子点。当主程序执行到该钩子点时,会检查并依次调用所有已注册的钩子函数。 2、相关概念区分 Hook vs 事件监听:非常相似,有时可互换。但Hook概念更侧重在系统流程内部的关键点插入逻辑,而事件监听更侧重对外部异步事件(如用户点击)的响应。Hook是事件监听的一种实现方式。 Hook vs 中间件:中间件通常是一个完整的处理链,可以中断或传递请求,是Hook模式在请求-响应流程中的一种具体应用形式。 Hook vs 继承/多态:目的都是扩展行为,但Hook通过注册回调实现,更灵活、更松耦合;继承是编译时的静态绑定。

软件和硬件中间件#

维度硬件中间件软件中间件(如Java后端)
核心目的屏蔽硬件差异,提供统一的硬件访问接口提供分布式通用服务,解决应用间通信与集成问题
抽象对象物理硬件(CPU、传感器、外设)的多样性分布式系统中的通用能力(通信、事务、缓存等)
典型位置紧贴硬件之上,操作系统之下或之中操作系统之上,业务应用之下
关注重点实时性、资源效率、硬件兼容性吞吐量、可用性、数据一致性、开发效率
技术举例嵌入式协议栈、HAL、RTOS服务组件消息队列、应用服务器、分布式缓存、RPC框架

解耦#

解耦 的核心是降低模块间的依赖程度,使得一个模块的变化不会或尽可能少地影响到其他模块。

关键在于“依赖”:

  • 强耦合:模块A直接调用模块B的内部函数、访问其内部数据结构、或与其紧密同步交互。B一改,A必受影响。
  • 弱耦合/解耦:模块A通过一个稳定的接口、消息或事件与模块B交互,不关心B内部如何实现。

如何实现解耦?

  1. 面向接口编程:依赖抽象接口,而非具体实现类。这是Java等语言解耦的基石。
  2. 消息队列/事件驱动:服务A完成任务后,向队列发送一个“订单完成”事件,然后就不再关心谁来处理。服务B监听这个事件并发送短信。A和B互相不知道对方的存在。
  3. 依赖注入:将依赖关系从类内部创建改为由外部容器(如Spring)注入,进一步实现控制反转。
  4. 引入中间层/网关:如API网关统一处理鉴权、路由,让内部服务无需关心这些横切关注点。

设置和删除空闲钩子#

空闲钩子函数是空闲线程的钩子函数,如果设置了空闲钩子函数,就可以在系统执行空闲线程时,自动执行空闲钩子函数来做一些其他事情,比如系统指示灯。设置 / 删除空闲钩子的接口如下:

rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));复制错误复制成功

设置空闲钩子函数 rt_thread_idle_sethook() 的输入参数和返回值如下表所示:

函数参数描述
hook设置的钩子函数
返回——
RT_EOK设置成功
-RT_EFULL设置失败

删除空闲钩子函数 rt_thread_idle_delhook() 的输入参数和返回值如下表所示:

函数参数描述
hook删除的钩子函数
返回——
RT_EOK删除成功
-RT_ENOSYS删除失败

屏幕#

不管什么类型的屏幕,他的作用都是提供一个人机交互的操作界面,显示屏幕的运行信息,仙子啊几乎所有的电子设备都有屏幕,空调、微波炉、洗衣机,有些设备支持触摸、有些设备不支持触摸

常见屏幕类型#

LED屏 户外广告牌、会议时间显示 LED 点阵屏 电梯、欢迎标识、红绿灯、断码式液晶屏、仪器仪表。 LCD 不能自发光,需要借助背光源,显示色彩没有OLED鲜艳、功耗较低,起步较早、技术比较成熟,逐渐被OLED替代掉、成本较低、厚度也会比OLED厚一些。 OLED 自发光、显示颜色更加的鲜艳、同时成本较高、功耗也会比LCD高一些、但是显示效果更好,厚度较薄内部无复杂结构,可以弯曲作为曲面屏,用于高性能显示效果比较高的领域。 串口屏 迪文串口屏 上位机 TN VA IPS

屏幕基本参数#

  • 尺寸:一般是屏幕斜对角线长度 用英寸来表示 1英寸是2.54cm 我们使用的是2.8英寸
  • 像素:图像显示的最小单元
  • 分辨率:屏幕的长和宽分别能够显示多少像素点 240*320
  • 排列方式有关:钻石排列、周冬雨排列
  • 颜色显示:单色显示:只能显示蓝色、黄色、白色一种。彩色显示:三元色RGB RGB565 R5位 G6位 B5位 16位 两个字节 RGB888 24位 3个字节。1536000(总大小) 显示红色是 11111 000000 00000 四个十六进制数 0xf800 电脑屏幕一般是0-255 即888.
  • 显示格式:
  • 通信接口:由屏幕厂商决定
  • 传输数据方法:由屏幕厂商决定
  • 是否支持触摸:由屏幕厂商决定

并口通信:多位数据同时发送、典型时序的8080时序 GPIO 模拟读写时序 或者使用片上外设FSMC灵活静态存储控制器 只管读写数据即可 FSMC可以自动模拟8080时序进行通信

FSMC与ILI9341对应关系总览#

ILI9341 信号功能描述FSMC对应信号/原理关键说明与配置要点
DB[15:0]16位并行数据总线。FSMC_D[15:0]数据线直接相连。需在FSMC配置中选择16位总线宽度。
RS (或 D/CX)寄存器选择。低电平:写命令;高电平:写数据。FSMC_Ax(例如A0)这是实现内存映射的关键。将FSMC的某根地址线(如A0)连接至此引脚。访问不同地址时,该地址线电平自动变化,从而区分命令/数据。
CSX片选信号,低有效)。FSMC_NE[x](例如 NE1)连接到FSMC的存储块片选信号。不同NE线对应FSMC不同的BANK地址空间)。
WRX写使能,下降沿锁存数据。FSMC_NWE写信号线,由FSMC硬件在写操作时自动产生。
RDX读使能。FSMC_NOE读信号线,通常ILI9341读取操作较少。
RST复位引脚。普通GPIO复位时序需由单片机GPIO控制,不通过FSMC管理。
FSMC地址映射—内存访问模式这是软件控制的核心: •命令地址:如 0x60000000 (A0=0) • 数据地址:如 0x60020000 (A0=1) 向这些地址写值,FSMC会自动产生完整的8080写时序

初始化:先能够点亮屏幕 显示一个像素点 横坐标、纵坐标、颜色 显示线条和图形 显示字符串 显示汉字 显示图片 其中,字符、汉字、图片需要先进行取模、取模之后它才能找到对应的字模并显示

代码实现流程#

FSMC使用CubeMX直接用配置加两个配置文件 配置环境使用env、Comenv 先取模汉字或者图片 gif制作:画图打开的分辨率 取模软件和输入输出图像分别率大小一致性。

串口#

串行同步(异步)全双工通信

协议/标准类型/定位线数 (基本)通信方式拓扑结构最大传输距离典型速率范围主要应用场景
UART异步串行通信协议 (逻辑层)2 (TX, RX)全双工,异步9600 bps ~ 几 Mbps短设备调试、GPS、蓝牙模块通信
I2C同步串行总线 (板载)2 (SDA, SCL)半双工,同步多主多从1-2米 (板内)标准模式100kbps,高速模式400kbps,最高可达3.4Mbps连接板载传感器、EEPROM、RTC等低速外设
SPI同步串行接口 (板载)3+n (SCLK, MOSI, MISO, n*CS)全双工,同步主从通信1-2米 (板内)通常1-70 Mbps,可更高Flash存储器、高速ADC/DAC、显示屏
1-Wire单总线串行协议1 (DQ + GND)半双工,异步一主多从可达100米低速 (如16.3kbps)身份识别(如iButton)、温度传感器(如DS18B20)
TTLUART的物理层电平2 (TX, RX) + GND同UART点对点< 1米同UART芯片间、开发板间短距离直接连接
RS-232物理层接口标准3 (TXD, RXD, GND) 基本全双工,异步点对点约15米通常到115.2kbps,可更高老式PC串口、工业设备配置口、部分调制解调器
RS-485物理层接口标准2 (A, B) 半双工半双工,异步一主多从总线理论上可达1200米300 bps ~ 10 Mbps工业现场总线、楼宇自动化、多节点数据采集
CAN专业领域串行总线2 (CAN_H, CAN_L)半双工,异步多主最长10km (5kbps时)最高1Mbps (40米内)汽车电子、工业控制、电梯控制系统
I2S专用音频串行总线3-4 (SD, WS, SCK, MCLK)同步,全双工点对点/一主一从板内短距离取决于采样率(如44.1kHz * 16位 * 2 = 1.41Mbps)数字音频设备间传输(如MCU到音频编解码器)

根据传输的媒介分为 有线:串口、SPI、IIC、CAN 无线、蓝牙、Lora、Zigbee 串口是一种常见的通信协议,在芯片上就是指的是外设接口—>USART/UART通用同步异步收发器 同步:通信双方规定的有统一的时钟线,一般以主机时钟为准 异步:没有时钟线,需要规定波特率 波特率:每秒传输的二进制位数,反应传输的速率,单位是bps 串行:数据排列好顺序,一位一位的发送 并行:数据多位一起发送,占用引脚资源 根据工作方式 单工 传输方向固定,要么只能收要么只能发 半双工 可以实现双向传输、同一时刻只能被一方占用、只能收或者发 全双工 任何时刻都能实现双向传输 串口的作用:单片机与单片机、单片机与电脑、单片机与传感器模块之间进行通信。 单片机与单片机 主从模式 单片机与电脑之间 烧录\下载、调试打印 单片机与传感器之间 语音播报、语音识别、粉尘、GPS

物理接线#

协议/标准典型物理接口形式常见连接器关键针脚与接线要点
UART (TTL电平)排针/焊盘引脚、排针、接线端子基本四线:VCC, GND, TX, RX。接法:设备A的 TX接设备B的 RX,GND互连。若使用流控,还需连接 RTS与 CTS。
RS-232DB9 串口 (最常见)9针 D-Sub (DB9) 母头/公头核心三线制:TXD (Pin 3), RXD (Pin 2), GND (Pin 5)[-3]。连接时需交叉:一端的 TXD接另一端的 RXD。
RS-485接线端子/DB92-3位螺钉接线端子、DB9两线制半双工:接 Data+ (或 A) 和 Data- (或 B)[-3]。所有设备挂接在同一对总线上,总线两端需接120Ω终端电阻
CAN接线端子3-5位螺钉接线端子两线制:接 CAN_H 和 CAN_L。同样需要120Ω终端电阻,通常位于总线两端的设备上。
I2C排针/焊盘引脚、排针两线制:SDA (数据线) 和 SCL (时钟线)。所有设备并联,两条线均需接上拉电阻至电源。
SPI排针/焊盘引脚、排针至少三线:SCLK (时钟), MOSI (主机发), MISO (主机收)。每个从机需独立的 CS (片选) 信号线。

单端信号:通过信号线与地线之间的电压方位决定对应的高低电平 差分信号:两根信号线(D+、D-)之间的电压差值范围决定对应的高低电平

特性单端信号差分信号
信号定义一根信号线相对于公共地线(GND) 的电压。使用一对信号线(D+和D-),接收端检测两者间的电压差。
抗干扰能力弱。噪声会直接叠加在信号电压上,导致误判。极强。外部噪声会同等地耦合到两条线上,电压差基本不变,噪声被抵消。
电压摆幅需要较高的电压摆幅(如RS-232为±3V至±15V)来确保抗噪。只需较低的电压摆幅(如RS-485为±1.5V),系统更省电。
信号回路需要依赖地线形成电流回路,地线电位不稳定性直接影响信号。信号在一对线内部形成回路,不依赖地线,避免了“地弹”问题。
电路复杂性简单,成本低。需要差分驱动/接收器,电路略复杂,成本稍高。
传输距离/速度距离短,速度较低。距离长,速度可以很高。
电平标准逻辑“1” (Mark)逻辑“0” (Space)信号类型典型工作电压最大速率/距离 (典型值)关键特性与应用
TTL电平+2.4V ~ +5V0V ~ +0.8V单端信号 (对GND)3.3V 或 5V速率高 (MHz级),距离短 (<1m)芯片直接电平,用于MCU、片内通信。注意:不同电压系统(如5V与3.3V)直接连接可能损坏器件。
RS-232-15V ~ -3V+3V ~ +15V单端信号 (对GND)±12V 或 ±5V20kbps / 15m (标准)负逻辑、高电压。用于抗噪声,但功耗大、速度低。需专用芯片(如MAX232)与TTL转换。
RS-485D+ > D- 200mVD- > D+ 200mV差分信号 (电压差)±1.5V ~ ±5V (差分电压)10Mbps / 12m 或 100kbps / 1200m抗干扰强、可组网。需终端电阻(120Ω)。用于工业现场总线。
USBD+ > D- 200mV (高速)D- > D+ 200mV (高速)差分信号 (电压差)数据线约3.3VUSB2.0: 480Mbps / 5m协议复杂,集成电源与控制。必须主机控制,非简单串行电平。
CAN隐性 (1): CAN_H ≈ CAN_L ≈ 2.5V显性 (0): CAN_H ≈ 3.5V, CAN_L ≈ 1.5V差分信号 (电压差)2.0V ~ 7.0V (共模)1Mbps / 40m 或 5kbps / 10km高可靠、多主竞争。用于汽车、工业。需终端电阻(120Ω)。

不同标准之间需要转换电平、使用电平转换模块进行转换 USART1_TX PA9 复用推挽输出 USART1_RX PA10 浮空输入

USART引脚工作模式/配置对应的GPIO模式配置备注说明
USARTx_TX全双工模式推挽复用输出发送数据线。
半双工同步模式推挽复用输出在半双工同步中,用于双向数据传输。
USARTx_RX全双工模式浮空输入或带上拉输入接收数据线。
半双工同步模式未使用可配置为通用I/O,释放引脚资源。
USARTx_CK同步模式推挽复用输出仅用于同步模式,提供时钟信号。
USARTx_RTS硬件流量控制推挽复用输出由本机发出,通知对方“本机已准备好接收”。
USARTx_CTS硬件流量控制浮空输入或带上拉输入由本机检测,对方通过此线通知“对方可发送”。

数据协议 位协议 按位发送 1801 1开始位 8数据位 0校验位 1停止位 0x55 0101 0101 低位先行 字节协议 按字节发送 MQTT、MODBUS模式 开始位 规定低电平作为开始位->空闲时为高电平 数据位->真正要传输的数据 校验位 奇偶校验 停止位 规定高电平作为停止位->空闲时位低电平 低位先行

协议典型帧/包结构数据格式关键点与特点
TTL/UART空闲 → 起始位(1 bit) → 数据位 (5-9 bits) → 校验位 (可选) → 停止位 (1/1.5/2 bits) → 空闲异步、无时钟线,帧结构简单[-2]。最基础的串行通信帧格式。
RS-232同上在物理层使用负逻辑和更高电压传输,但数据格式与UART完全相同。
RS-485同上定义了差分信号的电气特性,但数据格式与UART相同。
USB一个事务通常包含:令牌包 → 数据包 (可选) → 握手包 (可选)复杂分层协议。包(Packet)是最小单位,多个包组成事务(Transaction)。由主机轮询发起,非简单串行流
CAN帧起始 → 仲裁场 → 控制场 → 数据场 (0-8字节) → CRC场 → ACK场 → 帧结束面向消息,非地址寻址。仲裁场(标识符ID) 定义了消息内容和优先级,用于总线仲裁

标志位 TXE(transfer empty):发送寄存器为空。 TC 发送完成:发送移位寄存器为控。 RXNE:接受寄存器非空 已经有数据到来。 IDLE:空闲标志所有数据接受完成后,产生空闲标准、搭配中断使用。

代码实现流程#

配置GPIO:开时钟 结构体参数赋值 模式、看对应的原理图引脚、翻转速率、初始化

配置串口:开时钟 结构体参数赋值 波特率 1801 硬件流控制关闭 全双工 发送、接收引脚都打开 初始化

中断(串口中断+NVIC配置)

对应的结构体为

USART_InitTypeDef 定义于文件“stm32f10x_usart.h”:
typedef struct
{
u32 USART_BaudRate;
u16 USART_WordLength;
u16 USART_StopBits;
u16 USART_Parity;
u16 USART_HardwareFlowControl;
u16 USART_Mode;
u16 USART_Clock;
u16 USART_CPOL;
u16 USART_CPHA;
u16 USART_LastBit;
} USART_InitTypeDef;

CMD使能 裸机或者使用操作系统都可以进行配置

RT-Thread进行配置两个版本#

  • 起始位:表示数据传输的开始,电平逻辑为 “0” 。
  • 数据位:可能值有 5、6、7、8、9,表示传输这几个 bit 位数据。一般取值为 8,因为一个 ASCII 字符值为 8 位。
  • 奇偶校验位:用于接收方对接收到的数据进行校验,校验 “1” 的位数为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性,使用时不需要此位也可以。
  • 停止位: 表示一帧数据的结束。电平逻辑为 “1”。
  • 波特率:串口通信时的速率,它用单位时间内传输的二进制代码的有效位(bit)数来表示,其单位为每秒比特数 bit/s(bps)。常见的波特率值有 4800、9600、14400、38400、115200等,数值越大数据传输的越快,波特率为 115200 表示每秒钟传输 115200 位数据。

访问串口设备#

应用程序通过 RT-Thread提供的 I/O 设备管理接口来访问串口硬件,相关接口如下所示:

函数描述
rt_device_find()查找设备
rt_device_open()打开设备
rt_device_read()读取数据
rt_device_write()写入数据
rt_device_control()控制设备
rt_device_set_rx_indicate()设置接收回调函数
rt_device_set_tx_complete()设置发送完成回调函数
rt_device_close()关闭设备

查找串口设备#

应用程序根据串口设备名称获取设备句柄,进而可以操作串口设备,查找设备函数如下所示,

rt_device_t rt_device_find(const char* name);
参数描述
name设备名称
返回——
设备句柄查找到对应设备将返回相应的设备句柄
RT_NULL没有找到相应的设备对象

一般情况下,注册到系统的串口设备名称为 uart0,uart1等,使用示例如下所示:

#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
static rt_device_t serial; /* 串口设备句柄 */
/* 查找串口设备 */
serial = rt_device_find(SAMPLE_UART_NAME);

打开串口设备#

通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备:

rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数描述
dev设备句柄
oflags设备模式标志
返回——
RT_EOK设备打开成功
-RT_EBUSY如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开
其他错误码设备打开失败

oflags 参数支持下列取值 (可以采用或的方式支持多种取值):

#define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式 */
/* 接收模式参数 */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收模式 */
/* 发送模式参数 */
#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送模式 */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送模式 */

串口数据接收和发送数据的模式分为 3 种:中断模式、轮询模式、DMA 模式。在使用的时候,这 3 种模式只能选其一,若串口的打开参数 oflags 没有指定使用中断模式或者 DMA 模式,则默认使用轮询模式。

DMA(Direct Memory Access)即直接存储器访问。 DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过 DMA 控制器为 RAM 与 I/O 设备开辟一条直接传送数据的通路,这就节省了 CPU 的资源来做其他操作。使用 DMA 传输可以连续获取或发送一段信息而不占用中断或延时,在通信频繁或有大段信息要传输时非常有用。

Note

注:* RT_DEVICE_FLAG_STREAM:流模式用于向串口终端输出字符串:当输出的字符是 "\n" (对应 16 进制值为 0x0A)时,自动在前面输出一个 "\r"(对应 16 进制值为 0x0D) 做分行。

流模式 RT_DEVICE_FLAG_STREAM 可以和接收发送模式参数使用或 “|” 运算符一起使用。

以中断接收及轮询发送模式使用串口设备的示例如下所示:

#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
static rt_device_t serial; /* 串口设备句柄 */
/* 查找串口设备 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);

若串口要使用 DMA 接收模式,oflags 取值 RT_DEVICE_FLAG_DMA_RX。以DMA 接收及轮询发送模式使用串口设备的示例如下所示:

#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
static rt_device_t serial; /* 串口设备句柄 */
/* 查找串口设备 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以 DMA 接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);

控制串口设备#

通过控制接口,应用程序可以对串口设备进行配置,如波特率、数据位、校验位、接收缓冲区大小、停止位等参数的修改。控制函数如下所示:

rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数描述
dev设备句柄
cmd命令控制字,可取值:RT_DEVICE_CTRL_CONFIG
arg控制的参数,可取类型: struct serial_configure
返回——
RT_EOK函数执行成功
-RT_ENOSYS执行失败,dev 为空
其他错误码执行失败

在serial.h里面,控制参数结构体 struct serial_configure 原型如下:

struct serial_configure
{
rt_uint32_t baud_rate; /* 波特率 */
rt_uint32_t data_bits :4; /* 数据位 */
rt_uint32_t stop_bits :2; /* 停止位 */
rt_uint32_t parity :2; /* 奇偶校验位 */
rt_uint32_t bit_order :1; /* 高位在前或者低位在前 */
rt_uint32_t invert :1; /* 模式 */
rt_uint32_t bufsz :16; /* 接收数据缓冲区大小 */
rt_uint32_t reserved :4; /* 保留位 */
};

RT-Thread 提供的配置参数可取值为如下宏定义:

/* 波特率可取值 */
#define BAUD_RATE_2400 2400
#define BAUD_RATE_4800 4800
#define BAUD_RATE_9600 9600
#define BAUD_RATE_19200 19200
#define BAUD_RATE_38400 38400
#define BAUD_RATE_57600 57600
#define BAUD_RATE_115200 115200
#define BAUD_RATE_230400 230400
#define BAUD_RATE_460800 460800
#define BAUD_RATE_921600 921600
#define BAUD_RATE_2000000 2000000
#define BAUD_RATE_3000000 3000000
/* 数据位可取值 */
#define DATA_BITS_5 5
#define DATA_BITS_6 6
#define DATA_BITS_7 7
#define DATA_BITS_8 8
#define DATA_BITS_9 9
/* 停止位可取值 */
#define STOP_BITS_1 0
#define STOP_BITS_2 1
#define STOP_BITS_3 2
#define STOP_BITS_4 3
/* 极性位可取值 */
#define PARITY_NONE 0
#define PARITY_ODD 1
#define PARITY_EVEN 2
/* 高低位顺序可取值 */
#define BIT_ORDER_LSB 0
#define BIT_ORDER_MSB 1
/* 模式可取值 */
#define NRZ_NORMAL 0 /* normal mode */
#define NRZ_INVERTED 1 /* inverted mode */
/* 接收数据缓冲区默认大小 */
#define RT_SERIAL_RB_BUFSZ 64

接收缓冲区:当串口使用中断接收模式打开时,串口驱动框架会根据 RT_SERIAL_RB_BUFSZ 大小开辟一块缓冲区用于保存接收到的数据,底层驱动接收到一个数据,都会在中断服务程序里面将数据放入缓冲区。

RT-Thread 提供的默认串口配置如下,即 RT-Thread 系统中默认每个串口设备都使用如下配置:

#define RT_SERIAL_CONFIG_DEFAULT \
{ \
BAUD_RATE_115200, /* 115200 bits/s */ \
DATA_BITS_8, /* 8 databits */ \
STOP_BITS_1, /* 1 stopbit */ \
PARITY_NONE, /* No parity */ \
BIT_ORDER_LSB, /* LSB first sent */ \
NRZ_NORMAL, /* Normal mode */ \
RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
0 \
}

[!Note]

注:默认串口配置接收数据缓冲区大小为 RT_SERIAL_RB_BUFSZ,即 64 字节。若一次性数据接收字节数很多,没有及时读取数据,那么缓冲区的数据将会被新接收到的数据覆盖,造成数据丢失,建议调大缓冲区,即通过 control 接口修改。在修改缓冲区大小时请注意,缓冲区大小无法动态改变,只有在 open 设备之前可以配置。open 设备之后,缓冲区大小不可再进行更改。但除缓冲区之外的其他参数,在 open 设备前 / 后,均可进行更改。

若实际使用串口的配置参数与默认配置参数不符,则用户可以通过应用代码进行修改。修改串口配置参数,如波特率、数据位、校验位、缓冲区接收 buffsize、停止位等的示例程序如下:

#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
static rt_device_t serial; /* 串口设备句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
/* step1:查找串口设备 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* step2:修改串口配置参数 */
config.baud_rate = BAUD_RATE_9600; //修改波特率为 9600
config.data_bits = DATA_BITS_8; //数据位 8
config.stop_bits = STOP_BITS_1; //停止位 1
config.bufsz = 128; //修改缓冲区 buff size 为 128
config.parity = PARITY_NONE; //无奇偶校验位
/* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
/* step4:打开串口设备。以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);

发送数据#

向串口中写入数据,可以通过如下函数完成:

rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
参数描述
dev设备句柄
pos写入数据偏移量,此参数串口设备未使用
buffer内存缓冲区指针,放置要写入的数据
size写入数据的大小
返回——
写入数据的实际大小如果是字符设备,返回大小以字节为单位;
0需要读取当前线程的 errno 来判断错误状态

调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的大小是 size。

向串口写入数据示例程序如下所示:

#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
static rt_device_t serial; /* 串口设备句柄 */
char str[] = "hello RT-Thread!\r\n";
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 配置参数 */
/* 查找串口设备 */
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 发送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));

设置发送完成回调函数#

在应用程序调用 rt_device_write() 写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示 :

rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
参数描述
dev设备句柄
tx_done回调函数指针
返回——
RT_EOK设置成功

调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由设备驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。

设置接收回调函数#

可以通过如下函数来设置数据接收指示,当串口收到数据时,通知上层应用线程有数据到达 :

rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size));
参数描述
dev设备句柄
rx_ind回调函数指针
dev设备句柄(回调函数参数)
size缓冲区数据大小(回调函数参数)
返回——
RT_EOK设置成功

该函数的回调函数由调用者提供。若串口以中断接收模式打开,当串口接收到一个数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把串口设备句柄放在 dev 参数里供调用者获取。

若串口以 DMA 接收模式打开,当 DMA 完成一批数据的接收后会调用此回调函数。

一般情况下接收回调函数可以发送一个信号量或者事件通知串口数据处理线程有数据到达。使用示例如下所示:

#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
static rt_device_t serial; /* 串口设备句柄 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */
/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static int uart_sample(int argc, char *argv[])
{
serial = rt_device_find(SAMPLE_UART_NAME);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 初始化信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_input);
}

接收数据#

可调用如下函数读取串口接收到的数据:

rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size);
参数描述
dev设备句柄
pos读取数据偏移量,此参数串口设备未使用
buffer缓冲区指针,读取的数据将会被保存在缓冲区中
size读取数据的大小
返回——
读到数据的实际大小如果是字符设备,返回大小以字节为单位
0需要读取当前线程的 errno 来判断错误状态

读取数据偏移量 pos 针对字符设备无效,此参数主要用于块设备中。

串口使用中断接收模式并配合接收回调函数的使用示例如下所示:

static rt_device_t serial; /* 串口设备句柄 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */
/* 接收数据的线程 */
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
while (rt_device_read(serial, -1, &ch, 1) != 1)
{
/* 阻塞等待接收信号量,等到信号量后再次读取数据 */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* 读取到的数据通过串口错位输出 */
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}

关闭串口设备#

当应用程序完成串口操作后,可以关闭串口设备,通过如下函数完成:

rt_err_t rt_device_close(rt_device_t dev);
参数描述
dev设备句柄
返回——
RT_EOK关闭设备成功
-RT_ERROR设备已经完全关闭,不能重复关闭设备
其他错误码关闭设备失败

关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。

串口设备使用示例#

中断接收及轮询发送#

示例代码的主要步骤如下所示:

  1. 首先查找串口设备获取设备句柄。
  2. 初始化回调函数发送使用的信号量,然后以读写及中断接收方式打开串口设备。
  3. 设置串口设备的接收回调函数,之后发送字符串,并创建读取数据线程。
  • 读取数据线程会尝试读取一个字符数据,如果没有数据则会挂起并等待信号量,当串口设备接收到一个数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。
  • 此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。

运行序列图如下图所示:

串口中断接收及轮询发送序列图

/*
* 程序清单:这是一个 串口 设备使用例程
* 例程导出了 uart_sample 命令到控制终端
* 命令调用格式:uart_sample uart2
* 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
* 程序功能:通过串口输出字符串"hello RT-Thread!",然后错位输出输入的字符
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart2"
/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void serial_thread_entry(void *parameter)
{
char ch;
while (1)
{
/* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */
while (rt_device_read(serial, -1, &ch, 1) != 1)
{
/* 阻塞等待接收信号量,等到信号量后再次读取数据 */
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/* 读取到的数据通过串口错位输出 */
ch = ch + 1;
rt_device_write(serial, 0, &ch, 1);
}
}
static int uart_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* 查找系统中的串口设备 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_input);
/* 发送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_sample, uart device sample);

DMA 接收及轮询发送#

当串口接收到一批数据后会调用接收回调函数,接收回调函数会把此时缓冲区的数据大小通过消息队列发送给等待的数据处理线程。线程获取到消息后被激活,并读取数据。一般情况下 DMA 接收模式会结合 DMA 接收完成中断和串口空闲中断完成数据接收。

  • 此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。

运行序列图如下图所示:

串口DMA接收及轮询发送序列图

/*
* 程序清单:这是一个串口设备 DMA 接收使用例程
* 例程导出了 uart_dma_sample 命令到控制终端
* 命令调用格式:uart_dma_sample uart3
* 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
* 程序功能:通过串口输出字符串"hello RT-Thread!",并通过串口输出接收到的数据,然后打印接收到的数据。
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart3" /* 串口设备名称 */
/* 串口接收消息结构*/
struct rx_msg
{
rt_device_t dev;
rt_size_t size;
};
/* 串口设备句柄 */
static rt_device_t serial;
/* 消息队列控制块 */
static struct rt_messagequeue rx_mq;
/* 接收数据回调函数 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
struct rx_msg msg;
rt_err_t result;
msg.dev = dev;
msg.size = size;
result = rt_mq_send(&rx_mq, &msg, sizeof(msg));
if ( result == -RT_EFULL)
{
/* 消息队列满 */
rt_kprintf("message queue full!\n");
}
return result;
}
static void serial_thread_entry(void *parameter)
{
struct rx_msg msg;
rt_err_t result;
rt_uint32_t rx_length;
static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1];
while (1)
{
rt_memset(&msg, 0, sizeof(msg));
/* 从消息队列中读取消息*/
result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
if (result > 0)
{
/* 从串口读取数据*/
rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size);
rx_buffer[rx_length] = '\0';
/* 通过串口设备 serial 输出读取到的消息 */
rt_device_write(serial, 0, rx_buffer, rx_length);
/* 打印数据 */
rt_kprintf("%s\n",rx_buffer);
}
}
}
static int uart_dma_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
static char msg_pool[256];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* 查找串口设备 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 初始化消息队列 */
rt_mq_init(&rx_mq, "rx_mq",
msg_pool, /* 存放消息的缓冲区 */
sizeof(struct rx_msg), /* 一条消息的最大长度 */
sizeof(msg_pool), /* 存放消息的缓冲区大小 */
RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
/* 以 DMA 接收及轮询发送方式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_input);
/* 发送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_dma_sample, uart device dma sample);

实验效果如图: 输入图片说明

串口接收不定长数据#

串口接收不定长数据需要用户在应用层进行处理,一般会有特定的协议,比如一帧数据可能会有起始标记位、数据长度位、数据、终止标记位等,发送数据帧时按照约定的协议进行发送,接收数据时再按照协议进行解析。

以下是一个简单的串口接收不定长数据示例代码,仅做了数据的结束标志位 DATA_CMD_END,如果遇到结束标志,则表示一帧数据结束。示例代码的主要步骤如下所示:

  1. 首先查找串口设备获取设备句柄。
  2. 初始化回调函数发送使用的信号量,然后以读写及中断接收方式打开串口设备。
  3. 设置串口设备的接收回调函数,之后发送字符串,并创建解析数据线程。
  • 解析数据线程会尝试读取一个字符数据,如果没有数据则会挂起并等待信号量,当串口设备接收到一个数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。在解析数据时,判断结束符,如果结束,则打印数据。
  • 此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。
  • 当一帧数据长度超过最大长度时,这将是一帧不合格的数据,因为后面接收到的字符将覆盖最后一个字符。
/*
* 程序清单:这是一个串口设备接收不定长数据的示例代码
* 例程导出了 uart_dma_sample 命令到控制终端
* 命令调用格式:uart_dma_sample uart2
* 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
* 程序功能:通过串口 uart2 输出字符串"hello RT-Thread!",并通过串口 uart2 输入一串字符(不定长),再通过数据解析后,使用控制台显示有效数据。
*/
#include <rtthread.h>
#define SAMPLE_UART_NAME "uart2"
#define DATA_CMD_END '\r' /* 结束位设置为 \r,即回车符 */
#define ONE_DATA_MAXLEN 20 /* 不定长数据的最大长度 */
/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
/* 接收数据回调函数 */
static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
if (size > 0)
{
rt_sem_release(&rx_sem);
}
return RT_EOK;
}
static char uart_sample_get_char(void)
{
char ch;
while (rt_device_read(serial, 0, &ch, 1) == 0)
{
rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL);
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
return ch;
}
/* 数据解析线程 */
static void data_parsing(void)
{
char ch;
char data[ONE_DATA_MAXLEN];
static char i = 0;
while (1)
{
ch = uart_sample_get_char();
rt_device_write(serial, 0, &ch, 1);
if(ch == DATA_CMD_END)
{
data[i++] = '\0';
rt_kprintf("data=%s\r\n",data);
i = 0;
continue;
}
i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i;
data[i++] = ch;
}
}
static int uart_data_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* 查找系统中的串口设备 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_rx_ind);
/* 发送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 创建 serial 线程 */
rt_thread_t thread = rt_thread_create("serial", (void (*)(void *parameter))data_parsing, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_data_sample, uart device sample);

练习:接收线程创建,串口调试助手控制蜂鸣器 ‘A’->打开 ‘B’->关闭 方法一:给MSH线程换一个绑定串口 方法二:在创建串口线程成功之后将msh进程挂起 使用方法二比较合理

模块产品手册#

淘宝商家、bing、电子发烧友、嘉立创eda、AI DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数 字模块采集技术和温湿度传感技术,确保产品具有枀高的可靠性与卓越的长期稳定性。传感器包括一个电容式感湿元件和一个 NTC 测温元件,并与一个高性能 8 位单片机相连接。 成本低、长期稳定、相对湿度和温度测量、品质卓越、超快响应、抗干扰能力强、超长的信号传输距离、数字信号输出、精确校准。 温度和湿度的测量范围 温度:-2060℃ 湿度:595 %RH(相对湿度) 物理接线和传输的数据 1、VDD 供电 3.3~5.5V DC 2、DATA 串行数据,单总线 3、NC 空脚 4、GND 接地,电源负极 DATA,DHT11 器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。设备(主机或从机)通过一个漏极开路或三态端口连至该数据线,以允许设备在不发送数据时能够释放总线,而让其它设备使用总线;单总线通常要求外接一个约 4.7kΩ 的上拉电阻,这样,当总线闲置时,其状态为高电平。由于它们是主从结构,只有主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。只有一根数据传输线、数据的发送和接收都由这一条数据线完成。 8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位。

NOTE

注:其中湿度小数部分为 0

校验位数据定义 “8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据”8bit 校验位等于 所得结果的末 8 位 DATA 用于微处理器与 DHT11 之间的通讯和同步,采用单总线数据格式,一次传送 40 位数据,高位先出。 示例一:接收到的 40 位数据为: 0011 0101 0000 0000 0001 1000 0000 0100 0101 0001 湿度高 8 位 湿度低 8 位 温度高 8 位 温度低 8 位 校验位 计算: 0011 0101+0000 0000+0001 1000+0000 0100= 0101 0001 接收数据正确: 湿度:0011 0101(整数)=35H=53%RH 0000 0000(小数)=00H=0.0%RH =>53%RH + 0.0%RH = 53.0%RH 温度:0001 1000(整数)=18H=24℃ 0000 0100(小数)=04H=0.4℃ =>24℃ + 0.4℃ = 24.4℃

NOTE

注:主机从 DHT11 读取的温湿度数据总是前一次的测量值,如两次测间隔时间很长,请连续读两次以第二次获得的值为实时温湿度值。

外设读取步骤 主机和从机之间的通信可通过如下几个步骤完成(外设(如微处理器)读取 DHT11 的数据的步骤)。 步骤一: DHT11 上电后(DHT11 上电后要等待 1S 以越过不稳定状态在此期间不能发送任何指令),测试环境 温湿度数据,并记录数据,同时 DHT11 的 DATA 数据线由上拉电阻拉高一直保持高电平;此时 DHT11 的 DATA 引脚处于输入状态,时刻检测外部信号。 步骤二: 微处理器的 I/O 设置为输出同时输出低电平,且低电平保持时间不能小于 18ms(最大不得超过 30ms), 然后微处理器的 I/O 设置为输入状态,由于上拉电阻,微处理器的 I/O 即 DHT11 的 DATA 数据线也随之变 高,等待 DHT11 作出回答信号 步骤三: DHT11 的 DATA 引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后 DHT11 的 DATA引脚处于输出状态,输出 83 微秒的低电平作为应答信号,紧接着输出 87 微秒的高电平通知外设准备接收数据,微处理器的 I/O 此时处于输入状态,检测到 I/O 有低电平(DHT11 回应信号)后,等待 87 微秒的高电平后的数据接收 DHT11等待回传响应信号 DHT11给单片机发送温湿度数据 DHT11释放对应的数据总线信号 0:54us低+2327us高 1:54us低+6874us高 DHT11完成之后发送结束信号

数据时序#

代码实现 方法一:使用env终端menuconfig,选中对应的DHT11传感器模块组件 第一种方法会调用官方的rtthread 官方示例已经写好了对应的时序、其时序步骤经过严密的系统的考量、包括但不限于弱定义us延迟函数,不进行重复的编译、重置dht11状态,检查dht11引脚状态、单独读取比特位、整合为字节、40位比特数据移位和数据处理、新旧数据更新、温湿度缓冲池动态大小、内存管理、温湿度范围限制、设备注册和设备释放等等。 方法二:自己创建进程,对于其时序图,自己封装线程去采集接收处理DHT11数据

ADC#

ADC模数转换器、单元,将模拟量转化为数字量,因为单片机能够直接处理的是数字信号,但是生活中很多传感器采集到的是模拟量,比如—>光照、声音、气体浓度、电压、电流、所以需要ADC来进行辅助处理(需要转换成更容易储存、处理和发射的数字形式),与之相对应的 DAC(Digital-to-Analog Converter),它是 ADC 模数转换的逆向过程,ADC 最早用于对无线信号向数字信号转换。如电视信号,长短播电台发射接收等。 12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和2个内部 信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右 对齐方式存储在16位数据寄存器中。 模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。 ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。 12位分辨率 及其转换范围是0~4095

参数类别配置选项数值/说明
通道配置总通道数18 通道
外部通道16 通道
内部通道2 通道(18-16=2)
采集模式单次采集单次触发完成一次采集
连续采集连续不间断采集
扫描模式按预设顺序扫描多个通道
非扫描模式固定采集指定通道
分辨率ADC 位数12 位
转换范围0 ~ 4095 (2¹² = 4096)
数据对齐左对齐数据向左对齐存储
右对齐数据向右对齐存储(推荐/一般配置)
时钟配置输入时钟频率< 14 MHz(最大限制)
推荐工作频率根据具体应用配置
其他参数参考电压需根据具体芯片规格确定
采样时间需根据输入信号特性配置

转换过程#

如下图所示模数转换一般要经过采样、保持和量化、编码这几个步骤。在实际电路中,有些过程是合并进行的,如采样和保持,量化和编码在转换过程中是同时实现的。

ADC 转换过程

分辨率#

分辨率以二进制(或十进制)数的位数来表示,一般有8位、10位、12位、16位等,它说明模数转换器对输入信号的分辨能力,位数越多,表示分辨率越高,恢复模拟信号时会更精确。

精度#

精度表示 ADC 器件在所有的数值点上对应的模拟值和真实值之间的最大误差值,也就是输出数值偏离线性最大的距离。

Note

注:精度与分辨率是两个不一样的概念,请注意区分。

转换速率#

转换速率是指 A/D 转换器完成一次从模拟到数字的 AD 转换所需时间的倒数。例如,某 A/D 转换器的转换速率为 1MHz,则表示完成一次 AD 转换时间为 1 微秒。

访问 ADC 设备#

应用程序通过 RT-Thread 提供的 ADC 设备管理接口来访问 ADC 硬件,相关接口如下所示:

函数描述
rt_device_find()根据 ADC 设备名称查找设备获取设备句柄
rt_adc_enable()使能 ADC 设备
rt_adc_read()读取 ADC 设备数据
rt_adc_disable()关闭 ADC 设备

查找 ADC 设备#

应用程序根据 ADC 设备名称获取设备句柄,进而可以操作 ADC 设备,查找设备函数如下所示:

rt_device_t rt_device_find(const char* name);
参数描述
nameADC 设备名称
返回——
设备句柄查找到对应设备将返回相应的设备句柄
RT_NULL没有找到设备

一般情况下,注册到系统的 ADC 设备名称为 adc0,adc1等,使用示例如下所示:

#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */
rt_adc_device_t adc_dev; /* ADC 设备句柄 */
/* 查找设备 */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);

使能 ADC 通道#

在读取 ADC 设备数据前需要先使能设备,通过如下函数使能设备:

rt_err_t rt_adc_enable(rt_adc_device_t dev, rt_uint32_t channel);
参数描述
devADC 设备句柄
channelADC 通道
返回——
RT_EOK成功
-RT_ENOSYS失败,设备操作方法为空
其他错误码失败

使用示例如下所示:

#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */
#define ADC_DEV_CHANNEL 5 /* ADC 通道 */
rt_adc_device_t adc_dev; /* ADC 设备句柄 */
/* 查找设备 */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
/* 使能设备 */
rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);

读取 ADC 通道采样值#

读取 ADC 通道采样值可通过如下函数完成:

rt_uint32_t rt_adc_read(rt_adc_device_t dev, rt_uint32_t channel);
参数描述
devADC 设备句柄
channelADC 通道
返回——
读取的数值

使用 ADC 采样电压值的使用示例如下所示:

#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */
#define ADC_DEV_CHANNEL 5 /* ADC 通道 */
#define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/
#define CONVERT_BITS (1 << 12) /* 转换位数为12位 */
rt_adc_device_t adc_dev; /* ADC 设备句柄 */
rt_uint32_t value, vol;
/* 查找设备 */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
/* 使能设备 */
rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
/* 读取采样值 */
value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL);
/* 转换为对应电压值 */
vol = value * REFER_VOLTAGE / CONVERT_BITS;
rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);

实际电压值的计算公式为:采样值 * 参考电压 / (1 << 分辨率位数),上面示例代码乘以 100 将数据放大,最后通过 vol / 100 获得电压的整数位值,通过 vol % 100 获得电压的小数位值。

关闭 ADC 通道#

关闭 ADC 通道可通过如下函数完成:

rt_err_t rt_adc_disable(rt_adc_device_t dev, rt_uint32_t channel);
参数描述
devADC 设备句柄
channelADC 通道
返回——
RT_EOK成功
-RT_ENOSYS失败,设备操作方法为空
其他错误码失败

使用示例如下所示:

#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */
#define ADC_DEV_CHANNEL 5 /* ADC 通道 */
rt_adc_device_t adc_dev; /* ADC 设备句柄 */
rt_uint32_t value;
/* 查找设备 */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
/* 使能设备 */
rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
/* 读取采样值 */
value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL);
/* 转换为对应电压值 */
vol = value * REFER_VOLTAGE / CONVERT_BITS;
rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);
/* 关闭通道 */
rt_adc_disable(adc_dev, ADC_DEV_CHANNEL);

FinSH 命令#

在使用设备前,需要先查找设备是否存在,可以使用命令 adc probe 后面跟注册的 ADC 设备的名称。如下所示:

msh >adc probe adc1
probe adc1 success

使能设备的某个通道可以使用命令 adc enable 后面跟通道号。

msh >adc enable 5
adc1 channel 5 enables success

读取 ADC 设备某个通道的数据可以使用命令 adc read 后面跟通道号。

msh >adc read 5
adc1 channel 5 read value is 0x00000FFF
msh >

关闭设备的某个通道可以使用命令 adc disable 后面跟通道号。

msh >adc disable 5
adc1 channel 5 disable success
msh >

ADC 设备使用示例#

光照检测 光照传感器

光敏电阻

光敏电阻本身也是一个阻值可变的电阻,阻值改变会影响电压的改变,ADC转换量也会改变->反映光照的强弱,光照越强->光敏电阻阻值越小 电压越小

ADC 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:

  1. 首先根据 ADC 设备名称 “adc1” 查找设备获取设备句柄。
  2. 使能设备后读取 adc1 设备对应的通道 5 的采样值,然后根据分辨率为 12 位,参考电压为 3.3V 计算实际的电压值。
  3. 最后关闭 ADC 设备对应通道。

运行结果:打印实际读取到的转换的原始数据和经过计算后的实际电压值。

/*
* 程序清单: ADC 设备使用例程
* 例程导出了 adc_sample 命令到控制终端
* 命令调用格式:adc_sample
* 程序功能:通过 ADC 设备采样电压值并转换为数值。
* 示例代码参考电压为3.3V,转换位数为12位。
*/
#include <rtthread.h>
#include <rtdevice.h>
#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */
#define ADC_DEV_CHANNEL 5 /* ADC 通道 */
#define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/
#define CONVERT_BITS (1 << 12) /* 转换位数为12位 */
static int adc_vol_sample(int argc, char *argv[])
{
rt_adc_device_t adc_dev;
rt_uint32_t value, vol;
rt_err_t ret = RT_EOK;
/* 查找设备 */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME);
if (adc_dev == RT_NULL)
{
rt_kprintf("adc sample run failed! can't find %s device!\n", ADC_DEV_NAME);
return RT_ERROR;
}
/* 使能设备 */
ret = rt_adc_enable(adc_dev, ADC_DEV_CHANNEL);
/* 读取采样值 */
value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL);
rt_kprintf("the value is :%d \n", value);
/* 转换为对应电压值 */
vol = value * REFER_VOLTAGE / CONVERT_BITS;
rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100);
/* 关闭通道 */
ret = rt_adc_disable(adc_dev, ADC_DEV_CHANNEL);
return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(adc_vol_sample, adc voltage convert sample);

语音播报#

模块介绍 MY1680 只能播报 不能识别 要将想要播报的语音和对应的音乐复制上去支持mp3和wav文件,MY1680-12P 是深圳市千佰度电子科技有限公司自主研发的一款小巧的微集成MP3模块。采用MY1680 SOP16 MP3主控芯片,支持MP3、WAV格式双解码。模块内置FLASH存储芯片,1-16M容量可选;也可外接 U盘或USB数据线连接电脑更换FLASH的音频文件。该模块内置3W功放,可以直接驱动3W的喇叭,使用更方便。24 位 DAC 输出,动态范围支持 93dB,信噪比支持 85dB,完全支持 FAT16、FAT32 文件系统,最大支持 16M FLASH,32G 的 U 盘。支持 UART 异步串口控制:支持播放、暂停、上下曲、音量加减、选曲播放、插播等。ADKEY 功能,通过电阻选择可实现标准 MP3 功能的 5 按键控制和其他功能。内置音量、曲目掉电记忆功能。采样率8~48K、比特率8~320Kbps 音频文件,标准串口,3.3V TTL 电平,波特率9600,接3W/4Ω或1W/8Ω喇叭 ,MY1680-12P内置标准UART异步串口接口,为3.3V TTL电平接口。可通过MAX3232芯片转换成RS232电平或者通 过 USB转TTL模块与PC通讯进行调试。通讯数据格式是:起始位:1位;数据位:8位;奇偶位:无;停止位:1位。还是1801

协议命令格式#

所有数据均为十六进制数。

字段描述示例值
起始码命令开始标志,固定值7E
长度从“长度”字段到“校验码”字段的字节总数(十六进制)04
操作码指令代码(十六进制)31
参数可选参数,可能没有或为两个字节(十六进制)19
校验码“长度”、“操作码”、“参数”依次异或的值(十六进制)2C
结束码命令结束标志,固定值EF

注意:

  • “长度”字段的计算:统计从“长度”到“校验码”的所有字节个数(十六进制表示)。例如,示例中长度值为 04,表示有 04、31、19、2C 共 4 个字节。
  • “校验码”的计算:依次对“长度”、“操作码”、“参数”进行异或运算。例如,04 xor 31 xor 19 = 2C。
  • 示例指令:设置音量指令为 7E 04 31 19 2C EF。
  • 长度 = 长度 操作码 校验码 结束码 (起始码是固定的)

RFID#

什么是RFID#

无线射频识别是通过无线电波实现读卡器和电子标签(应答器)的非接触式自动识别技术,可精准识别目标,广泛应用于动物识别、汽车防盗、门禁管理、生产线自动化及物料追踪等领域。一个完整的RFID系统由读写器、应答器、软件系统组成

应答器(电子标签)#

接触式:水卡、燃气卡、银行卡、电卡 非接触式、门禁卡

S50非接触IC卡性能简介

容量8K位 EEPROM 1024字节

分为16个扇区,每个扇区为4块,每块16个字节,以块为存取单位

每个扇区有独立的一组密码及访问控制

每张卡有唯一序列号,为32位

具有防冲突机制,支持多卡操作

无电源,自带天线,内含加密控制逻辑和通讯逻辑电路

数据保存期为10年,可改写10万次,读无限次

工作温度:-20℃~50℃(湿度为90%)

工作频率:13.56MHZ 高频 类似身份证

通信速率:106 KBPS

读写距离:10 cm以内(与读写器有关)

1、第0扇区的块0(即绝对地址0块),它用于存放厂商代码,已经固化,不可更改。

2、每个扇区的块0、块1、块2为数据块,可用于存贮数据。

数据块可作两种应用:

用作一般的数据保存,可以进行读、写操作

用作数据值,可以进行初始化值、加值、减值、读值操作。

每个扇区的块3为控制块,包括了密码A、存取控制、密码B。

密码A(6字节) 存取控制(4字节) 密码B(6字节)

每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取控制。存取控制为4个字节,共32位,扇区中的每个块(包括数据块和控制块)的存取条件是由密码和存取控制共同决定的,在存取控制中每个块都有相应的三个控制位

块0: C10 C20 C30

块1: C11 C21 C31

块2: C12 C22 C32

块3: C13 C23 C33

对块0的控制:

字节bit 7bit 6bit 5bit 4bit 3bit 2bit 1bit 0
6C20_bC10_b------
7C10C30_b------
8C30C20------
9--------
( 注: C10_b表示C10取反 )

工作原理#

卡片的电气部分只由一个天线和ASIC组成。

天线:卡片的天线是只有几组绕线的线圈,很适于封装到IS0卡片中。

ASIC:卡片的ASIC由一个高速(106KB波特率)的RF接口,一个控制单元和一个

8K位EEPROM组成。

工作原理:读写器向M1卡发一组固定频率的电磁波,卡片内有一个LC串联谐振电路,其频率与读写器发射的频率相同,在电磁波的激励下,LC谐振电路产生共振,从而使电容内有了电荷,在这个电容的另一端,接有一个单向导通的电子泵,将电容内的电荷送到另一个电容内储存,当所积累的电荷达到2V时,此电容可做为电源为其它电路提供工作电压,将卡内数据发射出去或接取读写器的数据。

复位应答(Answer to request)

M1射频卡的通讯协议和通讯波特率是定义好的,当有卡片进入读写器的操作范围时,读写器以特定的协议与它通讯,从而确定该卡是否为M1射频卡,即验证卡片的卡型。

防冲突机制 (Anticollision Loop)

当有多张卡进入读写器操作范围时,防冲突机制会从其中选择一张进行操作,未选中的则处于空闲模式等待下一次选卡,该过程会返回被选卡的序列号。

选择卡片(Select Tag)

选择被选中的卡的序列号,并同时返回卡的容量代码。

三次互相确认(3 Pass Authentication)

选定要处理的卡片之后,读写器就确定要访问的扇区号,并对该扇区密码进行密码校验,在三次相互认证之后就可以通过加密流进行通讯。(在选择另一扇区时,则必须进行另一扇区密码校验。)

对数据块的操作

读 (Read):读一个块;

写 **(**Write):写一个块;

加(Increment):对数值块进行加值;

减(Decrement):对数值块进行减值;

存储(Restore):将块中的内容存到数据寄存器中;

传输(Transfer):将数据寄存器中的内容写入块中;

中止(Halt):将卡置于暂停工作状态;

MCU和其模块使用SPI通信 简单了解一下SPI通信

SPI通信#

在硬件层SPI通信有四根通信线:SCK(时钟线),MOSI(主机输出从机输入数据线),MISO(主机输入从机输出数据线),CS/SS(片选信号线)

MOSI 主机输出 从机输入 主机->从机

MISO 从机输出 主机输入 从机->主机

SCK时钟线

CS/SS 片选线 空闲时片选线为高电平 下降沿开始工作,主机与从机通信的时候要拉低

极性和相位

相位 从时钟线的第几个边沿(上升沿和下降沿)开始采样

0:从第一个边沿

1:从第二个边沿

0模式-----极性0,相位0 时钟线空闲时低电平,第一个沿(上升沿)开始采样

等字节交换(高位先行原则)

主机高位先发数据到从机低位通过MOSI、从机高位先发数据到主机的低位 通过MISO

WIFI#

wifi是一种常见的无线通信方式,它的工作原理是将宽带网线接入到路由器,路由器将有线信号调制成2.4Ghz或者5Ghz频段的射频信号 向外广播无线热点,终端设备去连接热点实现上网

WIFI芯片 ESP8266 WIFI模组ESP-12F

采用SMD-22封装 2400Mhz~2483.5Mhz 2.4Ghz频段

3.0V到3.6V 默认115200bps

安全性 WEP/WPA-PSK/WPA2-PSK

默认32Mbit

软件协议: AT指令集开发,厂家已经烧录好对应的AT固件 不同的AT指令对应不同的功能

2.2.1 功能说明 ATK-ESP8266 模块支持STA/AP/STA+AP 三种工作模式。

  • STA 模式:ESP8266模块通过路由器连接互联网,手机或电脑通过互联网实现对设备的远程控制。(站点模式)
  • AP 模式:默认模式ATK_ESP8266模块作为热点,实现手机或电脑直接与模块通信,实现局域网无线控制。 (接入点模式)
  • STA+AP 模式:两种模式的共存模式(混合模式),(STA模式)即可以通过路由器连接到 互联网,并通过互联网控制设备;(AP模式)也可作为wifi热点,其他wifi 设备连接到模块。这样实现局域网和广域网的无缝切换,方便操作。
    命令类型指令格式功能描述
    测试命令AT+<CMD>=?该命令用于查询设置命令或内部程序设置的参数以及其他范围。
    查询命令AT+<CMD>?该命令用于返回参数的当前值。
    设置命令AT+<CMD>=<...>该命令用于设置用户自定义的参数值。
    执行命令AT+<CMD>该命令用于执行受模块内部程序控制的变参数不可变的功能。

基础AT测试模块是否有问题、没有问题之后直接连接WIFI

设置对应的工作模式给定wifi名称和密码

AT+CWMODE= <mode> 1 2 3+Station

AT+CWJAP = <ssid>,<password>

调试打印->USART1 语音播报->USART2 WIFI->USART3 TX PB10 RX PB11

发布/订阅模式#

基本概念#

  • 订阅(Subscribe):设备订阅某个主题后,可以接收发送到该主题的所有消息

  • 发布(Publish):向指定主题发送消息,所有订阅该主题的设备都会收到

  • 连接信息#

    服务器配置#

    • 服务器地址:bemfa.com
    • 端口:8344

    消息格式#

    TCP协议使用键值对格式进行通信,字段间用 & 分隔,每条指令以 \r\n 结尾。

    字段说明#

    字段说明示例
    cmd消息类型(详见下表)cmd=1
    uid用户私钥(从控制台获取)uid=xxxxxxxx
    topic主题名称(设备标识)topic=light001
    msg消息内容msg=on

    消息类型(cmd)#

    值功能说明
    1订阅消息订阅主题,接收消息
    2发布消息向主题发送消息
    3订阅+历史订阅主题并拉取最新消息
    7获取时间获取服务器时间
    9获取历史拉取主题最新消息

RT-Thread常见错误 查表#

RT-Thread开发指北
https://fuwari.vercel.app/posts/rt-thread开发指北/
作者
Xiao Bai
发布于
2026-01-07
许可协议
CC BY-NC-SA 4.0