- 2025-01-13
-
回复了主题帖:
《Linux内核深度解析》-进程管理
heleijunjie72 发表于 2025-1-12 17:51
进程管理,是很很硬核的资料,很值得学习,收藏了
加油加油,一起努力
- 2025-01-10
-
回复了主题帖:
投票啦:图像处理、通信"小说"、仓颉编程、Altium书籍,先上哪一本?(人民邮电赞助)
更喜欢一些技术类的硬核书籍,更想要上线图像处理与计算机视觉实践——基于OpenCV和Python,系统了解下opencv的使用
-
发表了主题帖:
《Linux内核深度解析》-进程管理
本帖最后由 rtyu789 于 2025-1-10 00:30 编辑
# 进程管理
### 介绍
本章节主要从三个方面讲述
1. 进程,进程介绍,启动/退出进程
2. 进程调度
3. SMP调度
每个章节作者都是先介绍概念和原理,然后介绍用户空间的函数,从用户空间的用户态调用,深入到内核具体怎么操作的,讲解的十分细致
跟随着作者阅读源码,作者写的内容看起来已经十分丰富了,但是和实际的内核代码相比,内核代码还有更多更多细节。。。。甚至看一个函数,比如fork函数的实现,就需要花去一晚上的时间,真的是一杯茶一包烟一段函数看一天
平时在教科书上学习到的内容就是1,2两点截止了,本书中作者随着源码,阐述了一下SMP调度是怎么实现的,这种调度主要用在现在的多核处理器上,十分有学习的意义。但是由于我了解的不深,只能写一写概念性的东西了。
### 一、进程
进程是操作系统中的一个基本概念,指的是正在运行的程序实例。它不仅是程序的执行过程,还包括程序运行时所需要的内存、寄存器、文件等资源。
每个进程都有独立的地址空间,确保进程之间的隔离性。
书中展示了一些进程描述符 `task_struct` 的主要成员
| 成员名称 | 数据类型 | 描述 |
| ------------- | ---------------------- | ---------------------------------------------------------------- |
| `state` | `volatile long` | 进程的当前状态(如运行、就绪、阻塞等)。 |
| `stack` | `void *` | 指向进程内核栈的指针。 |
| `usage` | `refcount_t` | 进程的引用计数,用于跟踪进程的使用情况。 |
| `pid` | `pid_t` | 进程的唯一标识符(PID)。 |
| `tgid` | `pid_t` | 线程组 ID,表示进程所属的线程组(通常与主线程的 PID 相同)。 |
| `real_parent` | `struct task_struct *` | 指向进程的父进程的指针。 |
| `parent` | `struct task_struct *` | 指向进程的当前父进程的指针(可能因 `ptrace` 而改变)。 |
| `mm` | `struct mm_struct *` | 指向进程内存管理结构的指针,包含进程的地址空间信息。 |
| `active_mm` | `struct mm_struct *` | 指向进程当前活动的内存管理结构的指针。 |
| `comm` | `char[]` | 进程的可执行文件名(通常为 16 字节)。 |
| `prio` | `int` | 进程的动态优先级,用于调度器决策。 |
| `static_prio` | `int` | 进程的静态优先级,由用户或系统设置。 |
| `normal_prio` | `int` | 进程的归一化优先级,基于静态优先级和调度策略计算得出。 |
| `rt_priority` | `unsigned int` | 实时进程的优先级,用于实时调度策略。 |
| `sched_class` | `struct sched_class *` | 指向调度类结构的指针,决定进程的调度策略(如 CFS、实时调度等)。 |
| `exit_state` | `int` | 进程的退出状态,表示进程是否正在退出或已经退出。 |
| `exit_code` | `int` | 进程退出时的返回值,通常用于父进程获取子进程的退出状态。 |
linux4.12的源码,整个结构体的长度有600行左右,有大量的注释和宏定义判断,需慢慢了解各个成员的作用
```C++
struct task_struct {
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
void *stack;
atomic_t usage;
/* Per task flags (PF_*), defined further below: */
unsigned int flags;
unsigned int ptrace;
struct llist_node wake_entry;
int on_cpu;
/* Current CPU: */
unsigned int cpu;
unsigned int wakee_flips;
unsigned long wakee_flip_decay_ts;
struct task_struct *last_wakee;
int wake_cpu;
int on_rq;
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
struct task_group *sched_task_group;
struct sched_dl_entity dl;
unsigned int policy;
int nr_cpus_allowed;
cpumask_t cpus_allowed;
unsigned long rcu_tasks_nvcsw;
bool rcu_tasks_holdout;
struct list_head rcu_tasks_holdout_list;
int rcu_tasks_idle_cpu;
struct sched_info sched_info;
struct list_head tasks;
struct mm_struct *mm;
struct mm_struct *active_mm;
/* Per-thread vma caching: */
struct vmacache vmacache;
int exit_state;
int exit_code;
int exit_signal;
/* Bit to tell LSMs we're in execve(): */
unsigned in_execve:1;
unsigned in_iowait:1;
pid_t pid;
pid_t tgid;
/*
* Pointers to the (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
/* Real parent process: */
struct task_struct __rcu *real_parent;
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;
/*
* Children/sibling form the list of natural children:
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;
struct sysv_sem sysvsem;
struct sysv_shm sysvshm;
}
```
Linux 内核支持多种类型的命名空间,每种类型对应一种资源的隔离:
| 命名空间类型 | 隔离的资源 | 标志 (`clone` 或 `unshare`) |
| --------------- | ------------------------------ | --------------------------- |
| cgroup 命名空间 | 控制组(cgroup)视图 | `CLONE_NEWCGROUP` |
| IPC 命名空间 | System V IPC 和 POSIX 消息队列 | `CLONE_NEWIPC` |
| 网络命名空间 | 网络设备、IP 地址、路由表等 | `CLONE_NEWNET` |
| 挂载命名空间 | 文件系统挂载点 | `CLONE_NEWNS` |
| PID 命名空间 | 进程 ID(PID) | `CLONE_NEWPID` |
| 用户命名空间 | 用户和用户组 ID | `CLONE_NEWUSER` |
| UTS 命名空间 | 主机名和域名 | `CLONE_NEWUTS` |
![1_进程的命名空间](/data/attachment/forum/202501/10/002417e8gwpwp3mtgwpg08.jpg.thumb.jpg?rand=793.7677865435488)
最开始阅读的时候,感觉书籍中仅仅是简单的罗列,但是实际阅读后发现,每个成员在内核中都有实际的作用,书籍等于是把人引入门的作用
我自己在阅读的过程中,也是慢慢查找资料,了解各个变量的作用,并通过后面的章节,了解到变量会在什么时候会被用到
### 二、启动进程
进程的创建通常通过以下方式:
- fork() 是最常用的进程创建方式,适用于需要独立进程的场景
- vfork() 适用于子进程立即调用 exec() 的场景,性能优于 fork(),但使用限制较多
- clone() 提供了更灵活的控制,适用于创建线程或轻量级进程,是实现多线程编程的基础
![2_进程线程和链表](/data/attachment/forum/202501/10/002418hyauobafpa2fyxr1.jpg.thumb.jpg?rand=4773.287123322459)
```C++
#ifndef CONFIG_HAVE_COPY_THREAD_TLS
/* For compatibility with architectures that call do_fork directly rather than
* using the syscall entry points below. */
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
return _do_fork(clone_flags, stack_start, stack_size,
parent_tidptr, child_tidptr, 0);
}
#endif
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
{
struct task_struct *p;
int trace = 0;
long nr;
/*
* Determine whether and which event to report to ptracer. When
* called from kernel_thread or CLONE_UNTRACED is explicitly
* requested, no event is reported; otherwise, report if the event
* for the type of forking is enabled.
*/
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
...
wake_up_new_task(p);
...
}
```
![3_copy_process的执行流程](/data/attachment/forum/202501/10/002418pwfltwx1u196tetr.jpg.thumb.jpg?rand=7708.37148283347)
fork是用过系统调用sys_fork实现,sys_fork是通过do_fork实现,do_fork内部最终是由_do_fork实现,其中最核心的函数是copy_process,作者画了详细的调用流程,并对每一步走了解释,内容过多,就需要自己看啦
### 三、进程退出
1. 进程的终止方式
- 正常退出:进程执行完毕,调用 `exit()` 系统调用。
- 异常退出:进程由于错误或异常情况被迫终止,如段错误、除零错误等。
- 强制终止:由其他进程或操作系统强制终止,如通过 `kill` 命令。
![4_进程组退出时的执行流程](/data/attachment/forum/202501/10/002418kukrek6yxk5y9xux.jpg.thumb.jpg?rand=28.88649634224194)
2. 进程终止时的操作
- 释放资源:操作系统回收进程占用的内存、文件描述符等资源。
- 通知父进程:通过信号或进程间通信机制通知父进程子进程的终止状态。
- 更新进程表:将进程从进程表中移除,并将其状态设置为“终止”。
### 四、进程调度
1. 进程的状态
- 就绪状态
- 运行状态
- 轻度睡眠状态
- 中都睡眠状态
- 深度睡眠状态
- 僵尸状态
- 死亡状态
![5_进程的状态变迁](/data/attachment/forum/202501/10/002419fhuwyl6llssuaa6j.jpg.thumb.jpg?rand=6819.272541805505)
2. 调度的目标
- 公平性:确保每个进程都能获得合理的CPU时间。
- 高效性:最大化CPU利用率,减少空闲时间。
- 响应时间:确保交互式进程能够及时响应。
- 吞吐量:在单位时间内完成尽可能多的进程。
3. 进程调度类
进程调度类是 Linux 内核中用于管理和实现不同调度策略的框架。
它是 Linux 调度器(Scheduler)的核心组成部分,定义了如何选择下一个要运行的进程以及如何管理进程的调度行为。
1. stop_sched_class 停机调度类,最高优先级的调度类,用于执行停止 CPU 的任务(如迁移线程)
2. dl_sched_class 期限调度类,用于调度实时任务,基于最早截止时间优先(EDF)算法,确保任务在截止时间内完成
3. rt_sched_class 实时调度类,用于调度优先级固定的实时任务,确保高优先级任务优先执行
4. fair_sched_class 完全公平调度类(CFS),用于普通进程的调度,基于虚拟运行时间实现公平性
5. idle_sched_class 空闲调度类,当系统没有其他任务可运行时,调度空闲任务以降低功耗
![6_进程调度类](/data/attachment/forum/202501/10/002419aezpyzgyt8zpjytt.jpg.thumb.jpg?rand=4220.571229893655)
## 五、SMP调度
在多处理器系统(Symmetric Multiprocessing, SMP)中,多个CPU核心共享同一内存空间,操作系统需要扩展调度机制以充分利用多核资源。SMP调度不仅需要考虑单核调度的问题,还需要处理多核之间的负载均衡、缓存亲和性等问题。
### 1. 进程处理器的亲和性
#### 1.1 亲和性的概念
进程处理器的亲和性(Processor Affinity)是指进程或线程与特定CPU核心之间的绑定关系。通过设置亲和性,可以将进程或线程固定到某个CPU核心上运行,从而利用CPU缓存的局部性,减少缓存失效带来的性能损失。
#### 1.2 亲和性的类型
- 软亲和性:操作系统会尽量将进程调度到上次运行的CPU核心上,但不强制绑定。
- 硬亲和性:进程或线程被明确绑定到特定的CPU核心上,无法迁移到其他核心。
#### 1.3 亲和性的优点
- 减少缓存失效:进程在同一个CPU核心上运行,可以利用缓存中的数据,减少内存访问延迟。
- 提高性能:对于计算密集型任务,绑定到特定核心可以减少上下文切换的开销。
#### 1.4 亲和性的缺点
- 负载不均衡:如果某个核心上的进程负载过高,而其他核心空闲,可能导致系统整体性能下降。
- 灵活性降低:硬亲和性限制了进程的迁移能力,可能影响系统的动态负载均衡。
### 2. 迁移线程
线程迁移是指将正在运行的线程从一个CPU核心迁移到另一个核心的过程。迁移线程的目的是为了实现负载均衡,避免某些核心过载而其他核心空闲
#### 2.1 线程迁移的挑战
- 缓存失效:线程迁移会导致缓存中的数据失效,增加内存访问延迟。
- 上下文切换开销:迁移线程需要保存和恢复线程的上下文,增加了调度器的开销。
- 锁竞争:在多核系统中,线程迁移可能导致锁竞争,影响系统性能。
#### 2.2 线程迁移的策略
- 被动迁移:当某个核心过载时,调度器将部分线程迁移到其他核心上。
- 主动迁移:调度器定期检测系统的负载情况,主动将线程迁移到负载较轻的核心上。
- 亲和性优先:在迁移线程时,优先考虑线程的缓存亲和性,尽量减少缓存失效。
### 总结
进程管理是操作系统的核心功能之一,涉及进程的创建、调度、通信和终止等多个方面。通过合理的进程调度算法和进程间通信机制,操作系统能够高效地管理多个进程,确保系统的稳定性、响应性和公平性。同时,多线程技术的引入进一步提升了系统的并发处理能力,但也带来了同步和资源管理的挑战。理解进程管理的原理和机制,对于深入掌握操作系统的运行机制至关重要。
- 2025-01-07
-
加入了学习《全国大学生电子设计竞赛名师访谈》,观看 西安电子科技大学
-
回复了主题帖:
《Linux内核深度解析》-开箱以及环境准备
Timson 发表于 2025-1-2 17:20
您好!我们已经改好,谢谢您提的意见
哇,感谢,我看的确没有这个问题了,我查看了我历史的帖子,下面多的图片也都没有了,恢复正常了
- 2025-01-02
-
回复了主题帖:
祝福2025!回帖即有奖!选取最有心的送5块国产开发板!
lugl4313820 发表于 2025-1-2 11:06
恭喜大佬入选,烦请私信我收货信息。发的是武汉芯源CW32L021,包邮到家。
哇,感谢感谢,去了解了解这个芯片
- 2025-01-01
-
回复了主题帖:
《Linux内核深度解析》-开箱以及环境准备
nmg 发表于 2024-12-31 17:03
后面几张图片如果没用到,在上传的图片里删除,就不会显示了
我是用markdown编辑的,后面的图片都是正文中使用到的,我也一直想要搞掉后面的图片,但是没有找到方法
我试过先markdown引用图片,然后在上传的图片中删除,发出来帖子中的图片都看不到了
之前问了下管理员,说是没什么关系,那我就这样了
-
回复了主题帖:
祝福2025!回帖即有奖!选取最有心的送5块国产开发板!
好多没有见过的国产芯,2025加油加油
- 2024-12-26
-
发表了主题帖:
《Linux内核深度解析》-开箱以及环境准备
本帖最后由 rtyu789 于 2024-12-26 22:59 编辑
十分感谢EEWorld和人民邮电出版社提供了此次书籍阅读和分享的机会.
这本书是由异步社区出品的,是国内比较比较好的IT图书品牌,出版的IT书籍都比较的高质量。
现在公司主要是做嵌入式产品的,也是在Linux系统上做开发,只是用的内核还是Linux 3.x版本的。
市面上对于较新版本,深入介绍linux的书籍较少,也是有幸阅读这本书,加强对内核的了解。
# 一、书籍概览
这是书籍的大概展示,整体看起来还是不错的。里面的有丰富的流程图、表格,来解释内核的处理处理过程,还是很好的帮助理解的。整本书有将近600页左右,算是比较厚了
![1_书籍封面](/data/attachment/forum/202412/26/225449gf3ty3vh0t3rsm38.jpg.thumb.jpg?rand=1064.3384137323665)
![2_书籍背面](/data/attachment/forum/202412/26/225450rfovwt3zf2fwnvg9.jpg.thumb.jpg?rand=9617.737072495476)
![3_书籍插图1](/data/attachment/forum/202412/26/225450x5ac9ili4e4b2rl3.jpg.thumb.jpg?rand=5209.358224338476)
![4_书籍插图2](/data/attachment/forum/202412/26/225451qbdbbxb2owogrwzd.jpg.thumb.jpg?rand=7873.327410277045)
粗略的翻了翻书籍的目录,内存管理的内容有将近300页,占到书籍的二分之一了,果然内存管理是内核中非常重要的一部分了,阅读计划中的一章阅读完感觉是不太可能了。
从目录看到,书籍整体讲解的还是比较细致,对Linux内核的方方面面都会讲解到。同时由于书籍十分庞大,面面俱到的阅读也是不好的,引用作者在前言中的话:“学习内核关键是要理解数据结构之间的关系和函数调用关系。建议读者在学习时抓住主要线索,弄清楚执行流程,不要过多的关注函数的细节”
# 二、环境准备
1) 源码下载:linux-4.12.1
2) 阅读工具:
1. 作者推荐使用Source Insight,但是由于平时开发看代码都使用VSCode,所以也继续用这个了
2. 试了下,也不怎么卡顿,就是有的时候变量、函数转跳的时候有点吃力
3. 书本中对于代码片段都给出了详细的文件路径,未来阅读代码时候考虑使用手动查找目录的方式,提高对文件结构的熟悉度
4. 缺点,汇编文件不支持转跳,语法支持比较差,没有安装插件直接抓瞎
5. 推荐插件
1. C/C++、C/C++ Extension Pack、C/C++ Themes-C/C++语法支持
2. Makefile Tools-Makefile语法支持,很大部分的kbuild被VSCode自身Makefile语法支持了
3. kconfig-第三方小插件,源码中大量KConfig配置,支持较弱,
4. Arm Assembly-帮助.s文件高亮
5. MASM/TASM-汇编语言支持,
![5_VSCode目录结构](/data/attachment/forum/202412/26/225451kq9quitibapiqqqh.png?rand=6468.077792227984)
3) 作者推荐资料:
1. Cortex-A Series Programmer's Guide for ARMv8-A
1. 官网上只搜索到了一个在线的版本
2. 第三方网站找到了一个离线的版本
3. 链接都放在下方
2. ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile
1. 从官网可以下载到
4) 辅助工具:
大模型:现在大模型非常的多,我这边使用的是阿里的通义千问,这是看代码的一个很好的工具,特别对于内核的代码,有许多地方不明白为什么要这么设计的。
可以直接将自己不懂的代码抛给大模型,让他帮忙做出解释
![6_大模型示范](/data/attachment/forum/202412/26/225452ngq5v5rrwqccakbm.png.thumb.jpg?rand=8650.933138222133)
# 参考资料
[The Linux Kernel Archives](https://kernel.org/)
[kernal 4.12](https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/)
[Cortex-A Series Programmers Guide for ARMv8-A-ARM官网在线版本](https://developer.arm.com/documentation/den0013/d)
[Cortex-A Series Programmers Guide for ARMv8-A-从一个学校的网站上下载的](https://www.macs.hw.ac.uk/~hwloidl/Courses/F28HS/Docu/DEN0013D_cortex_a_series_PG.pdf)
[ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile](https://developer.arm.com/documentation/ddi0487/aa/?lang=en)
[The U-Boot Documentation](https://docs.u-boot.org/en/latest/index.html)
[sourceinsight](https://www.sourceinsight.com/)
- 2024-12-11
-
回复了主题帖:
读书入围名单: 《Linux内核深度解析》
个人信息无误,确认可以完成阅读分享计划
- 2024-11-23
-
回复了主题帖:
这5本书,你想让哪一本先上线?快来给它投票啦~
感觉最近测评书目中AI,大模型类型的书籍占比好高呀,最近几期全都是和AI相关的,还是想看一下和嵌入式软硬件相关的内容
投票给了AI编译器开发指南,想要多学习学习编译器相关方面的知识
- 2024-11-20
-
回复了主题帖:
(第10期)——玩具总动员 六爪机器人
这个画图真的太用心了,好可爱的机器人
-
回复了主题帖:
ST NUCLEO-WB09KE-BLE_Peripheral_Lite
Jacktang 发表于 2024-11-6 07:25
advertising的确是广告,官方的意思应该是让板子实现了模拟广告向外界发送信息的过程,,
官方也是用心 ...
的确是的,最开始我一直以为翻译错了,但是的确是这个单词
- 2024-11-06
-
发表了主题帖:
ST NUCLEO-WB09KE-BLE_Peripheral_Lite
# BLE_Peripheral_Lite
使用的例程
![1_BLE_Peripheral_Lite_例程](/data/attachment/forum/202411/05/235828yic2bcazbzcw8i22.png.thumb.jpg?rand=2341.0023126874903)
本例程演示了激活最少的蓝牙BLE外设功能的情况下进行蓝牙通讯
蓝牙Lite服务支持,仅仅激活了所需的最低功能(不包括任务序列器、定时器服务器、低功耗管理器等)
测试需要使用安装了ST BLE Sensor手机app应用
连接后,BLE_Peripheral_Lite可以从客户端接收写入消息并向其发送通知
# 程序功能流程
1. 程序开始运行时,使用本地名称 HELLO!进行数据广告
2. 在广告的过程中,绿色LED灯每0.5秒缓慢闪烁一次
3. 只能手机手机应用程序开始扫描
4. 在智能手机应用程序列表中选择 HELLO!设备并连接,绿色led切换速度更快,0.1秒一次
5. 进入P2P服务器界面,然后单击LED图标以打开/关闭LED1,控制的是Nucleo板0的蓝色LED
6. 服务器Nucleo板,每约1秒向智能手机客户端发送一次通知
7. 当外围设备断开连接时,0.5秒一次的广告将重新启动,并且可以再次连接到它
我一直觉得README中应该写的是0.5s一次的广播,但是原文如下,advertising的确是广告,官方的意思应该是让板子实现了模拟广告向外界发送信息的过程
- When the Peripheral device (Nucleo board) is disconnected, advertising is restarted and it is possible to connect to it again.
# 运行中的图示
![2_Hello界面](/data/attachment/forum/202411/05/235829gan60o0ndoc64pdz.jpg.thumb.jpg?rand=9791.716739154863)
![3_控制LED灯界面](/data/attachment/forum/202411/05/235830v3t2cazs4za14tbs.jpg.thumb.jpg?rand=7980.654300361325)
![4_消息通知界面](/data/attachment/forum/202411/05/235830itdtttd48tizijut.jpg.thumb.jpg?rand=5890.085250969341)
# 代码分析
```C++
int main(void)
{
HAL_Init();
SystemClock_Config();
PeriphCommonClock_Config();
MX_GPIO_Init();
MX_RADIO_Init();
MX_RADIO_TIMER_Init();
MX_PKA_Init();
MX_APPE_Init(NULL);
while (1)
{
MX_APPE_Process();
}
}
#define CFG_LPM_SUPPORTED (0)
void MX_APPE_Process(void)
{
VTimer_Process();
BLEStack_Process();
NVM_Process();
PERIPHERAL_LITE_SERVER_Process();
#if (CFG_LPM_SUPPORTED == 1)
PERIPHERAL_LITE_SERVER_Enter_LowPowerMode();
#endif /* CFG_LPM_SUPPORTED */
}
#define ADV_TIMEOUT_MS (500)
#define CONN_TIMEOUT_MS (100)
#define NOTIFICATION_TIMEOUT_MS (1 * 1000)
void PERIPHERAL_LITE_SERVER_Process(void)
{
if (bleAppContext.restartAdv)
{
bleAppContext.restartAdv = FALSE;
APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
}
/* Notification flow every ~ 1 sec */
if (bleAppContext.is_notification_timer_expired)
{
bleAppContext.is_notification_timer_expired = FALSE;
PERIPHERAL_LITE_SERVER_Switch_c_SendNotification();
/* Restart a timer for sending notification from server to client */
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Notification_mgr_timer_Id, NOTIFICATION_TIMEOUT_MS);
}
/* Led blinking rate */
if (bleAppContext.is_adv_connection_timer_expired)
{
bleAppContext.is_adv_connection_timer_expired = FALSE;
if (bleAppContext.BleApplicationContext_legacy.connectionHandle == 0xFFFF)
{
/* Start a timer for slow led blinking for advertising */
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
}
else
{
/* Start a timer for fast led blinking for connection */
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, CONN_TIMEOUT_MS);
}
}
}
```
可以看到主函数书中进入了循环MX_APPE_Process()
在这个函数PERIPHERAL_LITE_SERVER_Process()中不断处理Lite BLE的事件
可以看到,是由bleAppContext.is_adv_connection_timer_expired,这个标志位可以看到是否已经被连接,一定是别的地方使得这个标志位产生了变化
通过宏定义可以看到,在广告状态,就是未连接情况下,闪烁频率是500ms,连接后,闪烁频率变为100ms
```C++
uint32_t MX_APPE_Init(void *p_param)
{
UNUSED(p_param);
APP_DEBUG_SIGNAL_SET(APP_APPE_INIT);
#if (CFG_DEBUG_APP_ADV_TRACE != 0)
UTIL_ADV_TRACE_Init();
UTIL_ADV_TRACE_SetVerboseLevel(VLEVEL_L); /* functional traces*/
UTIL_ADV_TRACE_SetRegion(~0x0);
#endif
#if (CFG_LED_SUPPORTED == 1)
Led_Init();
#endif
#if (CFG_DEBUG_APP_TRACE != 0) && (CFG_DEBUG_APP_ADV_TRACE == 0)
COM_InitTypeDef COM_Init =
{
.BaudRate = 115200,
.WordLength= COM_WORDLENGTH_8B,
.StopBits = COM_STOPBITS_1,
.Parity = COM_PARITY_NONE,
.HwFlowCtl = COM_HWCONTROL_NONE
};
BSP_COM_Init(COM1, &COM_Init);
#endif
if (HW_RNG_Init() != HW_RNG_SUCCESS)
{
Error_Handler();
}
HW_AES_Init();
HW_PKA_Init();
APP_BLE_Init();
APP_DEBUG_SIGNAL_RESET(APP_APPE_INIT);
return STM32_BLE_SUCCESS;
}
void APP_BLE_Init(void)
{
ModulesInit();
BLE_Init();
bleAppContext.Device_Connection_Status = APP_BLE_IDLE;
bleAppContext.BleApplicationContext_legacy.connectionHandle = 0xFFFF;
bleAppContext.Advertising_mgr_timer_Id.callback = Adv_Connection;
bleAppContext.is_adv_connection_timer_expired = FALSE;
bleAppContext.Notification_mgr_timer_Id.callback = Notification_Flow;
bleAppContext.is_notification_timer_expired = FALSE;
bleAppContext.restartAdv = FALSE;
APP_DBG_MSG("\n");
APP_DBG_MSG("Services and Characteristics creation\n");
PERIPHERAL_LITE_SERVER_APP_Init();
APP_DBG_MSG("End of Services and Characteristics creation\n");
APP_DBG_MSG("\n");
APP_BLE_Procedure_Gap_Peripheral(PROC_GAP_PERIPH_ADVERTISE_START_FAST);
HAL_RADIO_TIMER_StartVirtualTimer(&bleAppContext.Advertising_mgr_timer_Id, ADV_TIMEOUT_MS);
bleAppContext.connIntervalFlag = 0;
return;
}
static void Adv_Connection(void *arg)
{
BSP_LED_Toggle(LED_GREEN);
bleAppContext.is_adv_connection_timer_expired = TRUE;
return;
}
```
可以看到,bleAppContext.is_adv_connection_timer_expired 标志位是在Adv_Connection函数中被改变了
Adv_Connection函数在APP_BLE_Init蓝牙的初始化中,被赋值作为了一个回调函数作为bleAppContext蓝牙app处理的上下文
并最终是由MX_APPE_Init函数调用,在主函数中被初始化,由此达到了有蓝牙连接控制LED灯闪烁的目的
# 演示视频
[localvideo]230b74e15d111b1cc1909ea06cb3cf35[/localvideo]
- 2024-11-05
-
发表了主题帖:
《RISC-V开放架构设计之道》-RV32CV+特权架构+可扩展选项
# RV32V 向量
![1_RV32V指令操作码](/data/attachment/forum/202411/05/005002gt10xo2krdcu0psy.jpg.thumb.jpg?rand=5372.011770651946)
向量操作主要用于数据级并行,最著名的数据级并行架构是SIMD,她将64位寄存器划分成了多个8位,16位或32位的片段,然后并行的计算他们,但是RISC-V使用向量架构进行实现
前面章节提到的每一条整数和浮点计算都有对应的向量版本
RV32V 添加了32个名称以V开头的向量寄存器,但是每个向量寄存器的元素不固定
虽然简单的向量处理器一次只能处理一个向量元素,但是根据定义,各个元素的操作相互独立,理论上处理器可以同时计算所有元素
![2_不同向量ISA的DAXPY指令和代码大小](/data/attachment/forum/202411/05/005003vuirr4eem4u8erej.jpg.thumb.jpg?rand=1161.5763078913942)
为了引入SIMD,MIPS-32 MSA和x86-32 AVX2都花费了更多的代码为引入SIMD主循环做准备,所以总指令数上,RV32V更少
# RV64 64位地址指令
RISC-V扩展为63位,只需要加入少量的指令:32位的字,双字和长字版本
![3_RV64位地址指令](/data/attachment/forum/202411/05/005004h9jnb8b4ohe0zzhh.jpg.thumb.jpg?rand=8610.8974008436)
RISC-V受益于同时设计32位和64为架构,但是较老的ISA不得不先后设计他们
放眼未来,最流行的64位指令集可能是ARM-64,RV64和x86-64
# RV32/64 特权架构
主要是为了嵌入式系统设计,目前介绍的指令都是在用户模式,RISV-V提供了两种新的特权模式:机器模式和监管模式,这两种模式的特权均高于用户模式
![4_RISV-V特权架构指令](/data/attachment/forum/202411/05/005004xby9ogvxhov9h8ky.jpg.thumb.jpg?rand=1751.7577720866618)
机器模式, 是RISC-V的硬件线程可以执行的最高特权模式,在这个模式下,硬件线程能完全访问内存,IO和底层系统功能
机器模式最重要的特点是拦截和处理异常,分为同步异常和中断
![5_RISV-V异常和中断的原因](/data/attachment/forum/202411/05/005005ut5tr1epy45ff0e2.jpg.thumb.jpg?rand=3037.143635600643)
监管模式,是可选的特选模式,目的在支持先对的UNIX操作系统,监管模式的特权高于用户模式的,但是低于机器模式
监管模式使用页式虚拟内存,将内存划分为固定大小的页用来进行地址的翻译和内存保护,启用分页时,大多数地址都是虚拟地址
# 未来RISC-V的可扩展选项
1. RV32B,位操作
2. RV32E,嵌入式
3. RV32H,支持虚拟机管理
4. RV32J,动态翻译语言
5. RV32L,十进制浮点
6. RV32N,用户态中断
7. RV32P,紧缩SIMD指令
8. RV32Q,四倍精度浮点
- 2024-10-22
-
发表了主题帖:
【Follow me第二季第2期】基础任务
# 任务一:驱动12x8点阵LED
![1_12x8点阵LED官方例程](/data/attachment/forum/202410/22/230144rlne0lhpeizneldl.png.thumb.jpg?rand=5326.602703566113)
```C++
/*
Single Frame
Displays single frames using matrix.loadFrame
See the full documentation here:
https://docs.arduino.cc/tutorials/uno-r4-wifi/led-matrix
*/
#include "Arduino_LED_Matrix.h" // Include the LED_Matrix library
#include "frames.h" // Include a header file containing some custom icons
ArduinoLEDMatrix matrix; // Create an instance of the ArduinoLEDMatrix class
void setup() {
Serial.begin(115200); // Initialize serial communication at a baud rate of 115200
matrix.begin(); // Initialize the LED matrix
}
void loop() {
// Load and display the "chip" frame on the LED matrix
matrix.loadFrame(chip);
delay(500); // Pause for 500 milliseconds (half a second)
// Load and display the "danger" frame on the LED matrix
matrix.loadFrame(danger);
delay(500);
// Load and display the "happy" frame on the LED matrix
matrix.loadFrame(happy);
delay(500);
// Load and display the "big heart" frame provided by the library
matrix.loadFrame(LEDMATRIX_HEART_BIG);
delay(500);
// Turn off the display
matrix.clear();
delay(1000);
// Print the current value of millis() to the serial monitor
Serial.println(millis());
}
```
官方的例程中定义了chip,danger,happy,LEDMATRIX_HEART_BIG这几个图形,通过延时来切换图形显示
![2_12x8点阵LED示意图](/data/attachment/forum/202410/22/230152jva69n19i47a5p9g.gif?rand=385.09205897547804)
# 任务二:用DAC生成正弦波
![3_用DAC生成正弦波官方例程](/data/attachment/forum/202410/22/230154eb0t9opg7djb0d7p.png.thumb.jpg?rand=9760.13954895486)
```C++
/*
SineWave
Generates a pre-generated sawtooth-waveform.
See the full documentation here:
https://docs.arduino.cc/tutorials/uno-r4-wifi/dac
*/
#include "analogWave.h" // Include the library for analog waveform generation
analogWave wave(DAC); // Create an instance of the analogWave class, using the DAC pin
int freq = 10; // in hertz, change accordingly
void setup() {
Serial.begin(115200); // Initialize serial communication at a baud rate of 115200
wave.sine(freq); // Generate a sine wave with the initial frequency
}
void loop() {
// Read an analog value from pin A5 and map it to a frequency range
freq = map(analogRead(A5), 0, 1024, 0, 10000);
// Print the updated frequency to the serial monitor
Serial.println("Frequency is now " + String(freq) + " hz");
wave.freq(freq); // Set the frequency of the waveform generator to the updated value
delay(1000); // Delay for one second before repeating
}
```
通过A0口发出DAC信号,通过A5口接受,在Arduino IDE上显示
![4_实物接线图](/data/attachment/forum/202410/22/230154arknbh5z2fwj89ko.jpg.thumb.jpg?rand=1524.4040964464123)
![5_Arduino_IDE读取波形](/data/attachment/forum/202410/22/230154dfyzfygeiyisdsk2.png.thumb.jpg?rand=2863.3041288188865)
# 任务三、四
用OPAMP放大DAC信号
用ADC采集并且打印数据到串口等其他接口可上传到上位机显示曲线
这两项由于没有示波器,所以就学习了一下代码
![6_用OPAMP放大DAC信号官方例程](/data/attachment/forum/202410/22/230155bzfsqfi4hmsps55s.png.thumb.jpg?rand=8585.88899265519)
```C++
#include
void setup () {
Serial.begin(9600);
delay(2000); // serial monitor delay
// activate OPAMP, default channel 0
// Plus: Analog A1
// Minus: Analog A2
// Output: Analog A3
if (!OPAMP.begin(OPAMP_SPEED_HIGHSPEED)) {
Serial.println("Failed to start OPAMP!");
}
bool const isRunning = OPAMP.isRunning(0);
if (isRunning) {
Serial.println("OPAMP running on channel 0!");
} else {
Serial.println("OPAMP channel 0 is not running!");
}
}
void loop() {
delay(1000); // do nothing
}
```
# 参考资料
[Arduino UNO R4 WiFi Digital-to-Analog Converter (DAC)](https://docs.arduino.cc/tutorials/uno-r4-wifi/dac)
[UNO R4 WiFi](https://docs.arduino.cc/hardware/uno-r4-wifi/)
- 2024-10-19
-
发表了主题帖:
《RISC-V开放架构设计之道》-RV32M+RV32MD+RV32A
# RV32M 乘法和除法指令
![1_RV32M指令操作码](/data/attachment/forum/202410/19/225336jylwybl2twyhhrtg.jpg.thumb.jpg?rand=8307.077413542345)
1. 乘法指令
1. 乘法,mul
2. 高位乘,mulh,可检查乘法溢出
3. 高位有符号-无符号乘,mulhsu,对多字的有符号乘法很有用
4. 高位无符号乘,mulhu,可检查乘法溢出
2. 除法指令
1. 除法,div
2. 无符号除法,divu
3. 求余指令
1. 求余,rem
2. 求无符号数的余,remu
整除法在处理器上经常是耗时的操作,有的时候可以用右移操作代替2的幂次的无符号除法
除数为常数时:先乘以一个近似的倒数,再矫正奇数高位的部分。
在早期的ARM-32中是不支持除法指令的,在2005年除法才被实现
# RV32F RV32D 单精度和双精度
![2_RV32F和RV32D指令操作码](/data/attachment/forum/202410/19/225337fde42revmvp3mvpu.jpg.thumb.jpg?rand=8111.960765857864)
RV32F和RV32D是单精度和双进度浮点指令,和其他的现代ISA一样,RISC-V遵循了IEEE 754-2019浮点标准
![3_RV32F和RV32D寄存器](/data/attachment/forum/202410/19/225338s0n70mro4smonlmm.jpg.thumb.jpg?rand=2602.0709873259284)
RV32F和RV32D使用了32个独立的F寄存器而非X寄存器,可以在不增加RISC-V指令格式中寄存器字段所占用空间的情况下,将寄存器容量和带宽翻倍
如果处理器同时处理支持RV32F和RV32D,但进度仅仅使用F寄存器的低32位
# RV32A 原子指令
![4_RV32A指令操作码](/data/attachment/forum/202410/19/225338xssnfr4fsmknxqsf.jpg.thumb.jpg?rand=9431.421143378957)
1. 内存原子操作,AMO
2. 加载保留/条件存储,LR/SC
原子操作指的是不可被中断的一个或一系列操作,在访问数据前加入了上锁机制,RISC-V提供了对原子指令的支持
# RV32C 压缩指令
![5_RV32C指令操作码](/data/attachment/forum/202410/19/225339x8z11fs3n6nzekw8.jpg.thumb.jpg?rand=483.61271408928764)
1. RV32V的设计:
1. 每条短指令都必须对应一条标准的32位RISC-V指令,此外16位的指令仅对汇编器和链接器可见,编译器开发者和汇编语言程序员无需关心
2. 处理器的设计者不能忽略RV32C,通过这个方法来实现降低开销:
1. 执行指令之前通过一个译码器将所有的16位指令转为32位指令
2. 一个不支持任何扩展的32位RISC-V最小处理器需要8000个门电路,译码器仅需要400个,所以开销很小
# 参考资料
[RISC-V指令集架构------RV32M乘法扩展指令集](https://blog.csdn.net/qq_38798111/article/details/129718824)
[RV32A:原子指令](https://xlinwei.github.io/docs/190720_RISC-V/risc06.html)
[使用Python 3 模拟RISC-V指令集C扩展(RV32C)中的指令功能](https://gitee.com/lwhengbit/rv32c/tree/master)
- 2024-10-17
-
加入了学习《Follow me II 2演示视频》,观看 Follow me II 2演示视频
- 2024-10-16
-
发表了主题帖:
【Follow me第二季第2期】入门任务
# 开箱
十分感谢得捷电子对本次活动的赞助,开发板的开箱照片
![1_开箱照片](/data/attachment/forum/202410/16/002539evbyz8ytwy33fu5b.jpg.thumb.jpg?rand=2690.8417481488577)
![2_开箱照片](/data/attachment/forum/202410/16/002539rxj8uijjuuj3j5uq.jpg.thumb.jpg?rand=4449.028228183285)
![3_开箱照片](/data/attachment/forum/202410/16/002539v92z99y9z9n2an0n.jpg.thumb.jpg?rand=3121.0106373025305)
![4_开箱照片](/data/attachment/forum/202410/16/002540qa0aa0a37xonm1m9.jpg.thumb.jpg?rand=5048.682388149384)
去官网下载Arduino IDE
![5_Arduino官网](/data/attachment/forum/202410/16/002540kbjmixatktgxuj4x.png.thumb.jpg?rand=1247.041007558023)
下载以后打开软件,需要下载Arduino UNO R4 WiFi对应的库文件,在设置和左边的菜单都中都可以选择
![6_Arduino选择库文件](/data/attachment/forum/202410/16/002540ie3a3jpvhf384a3a.png.thumb.jpg?rand=1389.716552812017)
插上Arduino UNO R4 WiFi开发板,就直接识别到了
![7_Arduino识别](/data/attachment/forum/202410/16/002541bdtb6tt8bp8q8cg8.png.thumb.jpg?rand=2236.2089401594344)
# 任务一:搭建环境并开启第一步Blink / 串口打印Hello EEWorld!
## 1)Blink
![8_Blink官方例程](/data/attachment/forum/202410/16/002541wcq1xvmc950qx1vz.png.thumb.jpg?rand=5944.079768515789)
这个是让开发板上LED自动闪烁的例程,直接在示例->basic->Blink中可以找到
官方写了详细的注释,甚至上放还提供了网址可以访问学习
```C++
/*
Blink
Turns an LED on for one second, then off for one second, repeatedly.
Most Arduinos have an on-board LED you can control. On the UNO, MEGA and ZERO
it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN is set to
the correct LED pin independent of which board is used.
If you want to know what pin the on-board LED is connected to on your Arduino
model, check the Technical Specs of your board at:
https://www.arduino.cc/en/Main/Products
modified 8 May 2014
by Scott Fitzgerald
modified 2 Sep 2016
by Arturo Guadalupi
modified 8 Sep 2016
by Colby Newman
This example code is in the public domain.
https://www.arduino.cc/en/Tutorial/BuiltInExamples/Blink
*/
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
```
编译代码,烧录到开发板,LED灯可以闪烁
![9_烧录成功](/data/attachment/forum/202410/16/002542gu2pmlg7yskssppw.png.thumb.jpg?rand=755.107040765548)
## 2)串口打印Hello EEWorld!
![10_串口官方例程](/data/attachment/forum/202410/16/002542uvg1r9y9vmi91jgy.png.thumb.jpg?rand=4416.733954248013)
官方提供了例程,直接在示例->Communication->SerialCallResponseASCII中可以找到
官方的例程是打印0,0,0,修改了Hello EEWorld!烧录,可以在串口助手中看到输出
```C++
/*
Serial Call and Response in ASCII
Language: Wiring/Arduino
This program sends an ASCII A (byte of value 65) on startup and repeats that
until it gets some data in. Then it waits for a byte in the serial port, and
sends three ASCII-encoded, comma-separated sensor values, truncated by a
linefeed and carriage return, whenever it gets a byte in.
The circuit:
- potentiometers attached to analog inputs 0 and 1
- pushbutton attached to digital I/O 2
created 26 Sep 2005
by Tom Igoe
modified 24 Apr 2012
by Tom Igoe and Scott Fitzgerald
Thanks to Greg Shakar and Scott Fitzgerald for the improvements
This example code is in the public domain.
https://www.arduino.cc/en/Tutorial/BuiltInExamples/SerialCallResponseASCII
*/
int firstSensor = 0; // first analog sensor
int secondSensor = 0; // second analog sensor
int thirdSensor = 0; // digital sensor
int inByte = 0; // incoming serial byte
void setup() {
// start serial port at 9600 bps and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
pinMode(2, INPUT); // digital sensor is on digital pin 2
establishContact(); // send a byte to establish contact until receiver responds
}
void loop() {
// if we get a valid byte, read analog ins:
if (Serial.available() > 0) {
// get incoming byte:
inByte = Serial.read();
// read first analog input:
firstSensor = analogRead(A0);
// read second analog input:
secondSensor = analogRead(A1);
// read switch, map it to 0 or 255
thirdSensor = map(digitalRead(2), 0, 1, 0, 255);
// send sensor values:
Serial.print(firstSensor);
Serial.print(",");
Serial.print(secondSensor);
Serial.print(",");
Serial.println(thirdSensor);
}
}
void establishContact() {
while (Serial.available()
- 2024-10-15
-
发表了主题帖:
ST NUCLEO-WB09KE-UART串口
CubeMX配置UART
![1_配置UART](/data/attachment/forum/202410/15/230533z8g6xtc8otxbcx9g.png.thumb.jpg?rand=3250.5230704587993)
主函数
```C++
int main(void)
{
HAL_Init();
SystemClock_Config();
PeriphCommonClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
uint8_t str_test1[] = "test str\n";
uint8_t str_test2[] = "Hello EEWorld\n";
while (1)
{
HAL_UART_Transmit(&huart1,(uint8_t*)str_test1, sizeof(str_test1)-1,100);
HAL_UART_Transmit(&huart1,(uint8_t*)str_test2, sizeof(str_test2)-1,100);
delay_ms(500);
}
}
```
UART初始化函数
```C++
/**
* @brief USART1 Initialization Function
* @param None
* @retval None
*/
static void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
/* USER CODE END USART1_Init 2 */
}
/**
* @brief Initialize the UART mode according to the specified
* parameters in the UART_InitTypeDef and initialize the associated handle.
* @param huart UART handle.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
/* Check the UART handle allocation */
if (huart == NULL)
{
return HAL_ERROR;
}
if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
{
/* Check the parameters */
assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance));
}
else
{
/* Check the parameters */
assert_param((IS_UART_INSTANCE(huart->Instance)) || (IS_LPUART_INSTANCE(huart->Instance)));
}
if (huart->gState == HAL_UART_STATE_RESET)
{
/* Allocate lock resource and initialize it */
huart->Lock = HAL_UNLOCKED;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
UART_InitCallbacksToDefault(huart);
if (huart->MspInitCallback == NULL)
{
huart->MspInitCallback = HAL_UART_MspInit;
}
/* Init the low level hardware */
huart->MspInitCallback(huart);
#else
/* Init the low level hardware : GPIO, CLOCK */
HAL_UART_MspInit(huart);
#endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */
}
huart->gState = HAL_UART_STATE_BUSY;
__HAL_UART_DISABLE(huart);
/* Perform advanced settings configuration */
/* For some items, configuration requires to be done prior TE and RE bits are set */
if (huart->AdvancedInit.AdvFeatureInit != UART_ADVFEATURE_NO_INIT)
{
UART_AdvFeatureConfig(huart);
}
/* Set the UART Communication parameters */
if (UART_SetConfig(huart) == HAL_ERROR)
{
return HAL_ERROR;
}
/* In asynchronous mode, the following bits must be kept cleared:
- LINEN and CLKEN bits in the USART_CR2 register,
- SCEN, HDSEL and IREN bits in the USART_CR3 register.*/
CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN));
__HAL_UART_ENABLE(huart);
/* TEACK and/or REACK to check before moving huart->gState and huart->RxState to Ready */
return (UART_CheckIdleState(huart));
}
/**
* @brief Set the TXFIFO threshold.
* @param huart UART handle.
* @param Threshold TX FIFO threshold value
* This parameter can be one of the following values:
* @arg @ref UART_TXFIFO_THRESHOLD_1_8
* @arg @ref UART_TXFIFO_THRESHOLD_1_4
* @arg @ref UART_TXFIFO_THRESHOLD_1_2
* @arg @ref UART_TXFIFO_THRESHOLD_3_4
* @arg @ref UART_TXFIFO_THRESHOLD_7_8
* @arg @ref UART_TXFIFO_THRESHOLD_8_8
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UARTEx_SetTxFifoThreshold(UART_HandleTypeDef *huart, uint32_t Threshold)
{
uint32_t tmpcr1;
/* Check parameters */
assert_param(IS_UART_FIFO_INSTANCE(huart->Instance));
assert_param(IS_UART_TXFIFO_THRESHOLD(Threshold));
/* Process Locked */
__HAL_LOCK(huart);
huart->gState = HAL_UART_STATE_BUSY;
/* Save actual UART configuration */
tmpcr1 = READ_REG(huart->Instance->CR1);
/* Disable UART */
__HAL_UART_DISABLE(huart);
/* Update TX threshold configuration */
MODIFY_REG(huart->Instance->CR3, USART_CR3_TXFTCFG, Threshold);
/* Determine the number of data to process during RX/TX ISR execution */
UARTEx_SetNbDataToProcess(huart);
/* Restore UART configuration */
WRITE_REG(huart->Instance->CR1, tmpcr1);
huart->gState = HAL_UART_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(huart);
return HAL_OK;
}
/**
* @brief Set the RXFIFO threshold.
* @param huart UART handle.
* @param Threshold RX FIFO threshold value
* This parameter can be one of the following values:
* @arg @ref UART_RXFIFO_THRESHOLD_1_8
* @arg @ref UART_RXFIFO_THRESHOLD_1_4
* @arg @ref UART_RXFIFO_THRESHOLD_1_2
* @arg @ref UART_RXFIFO_THRESHOLD_3_4
* @arg @ref UART_RXFIFO_THRESHOLD_7_8
* @arg @ref UART_RXFIFO_THRESHOLD_8_8
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UARTEx_SetRxFifoThreshold(UART_HandleTypeDef *huart, uint32_t Threshold)
{
uint32_t tmpcr1;
/* Check the parameters */
assert_param(IS_UART_FIFO_INSTANCE(huart->Instance));
assert_param(IS_UART_RXFIFO_THRESHOLD(Threshold));
/* Process Locked */
__HAL_LOCK(huart);
huart->gState = HAL_UART_STATE_BUSY;
/* Save actual UART configuration */
tmpcr1 = READ_REG(huart->Instance->CR1);
/* Disable UART */
__HAL_UART_DISABLE(huart);
/* Update RX threshold configuration */
MODIFY_REG(huart->Instance->CR3, USART_CR3_RXFTCFG, Threshold);
/* Determine the number of data to process during RX/TX ISR execution */
UARTEx_SetNbDataToProcess(huart);
/* Restore UART configuration */
WRITE_REG(huart->Instance->CR1, tmpcr1);
huart->gState = HAL_UART_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(huart);
return HAL_OK;
}
/**
* @brief Disable the FIFO mode.
* @param huart UART handle.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UARTEx_DisableFifoMode(UART_HandleTypeDef *huart)
{
uint32_t tmpcr1;
/* Check parameters */
assert_param(IS_UART_FIFO_INSTANCE(huart->Instance));
/* Process Locked */
__HAL_LOCK(huart);
huart->gState = HAL_UART_STATE_BUSY;
/* Save actual UART configuration */
tmpcr1 = READ_REG(huart->Instance->CR1);
/* Disable UART */
__HAL_UART_DISABLE(huart);
/* Disable FIFO mode */
CLEAR_BIT(tmpcr1, USART_CR1_FIFOEN);
huart->FifoMode = UART_FIFOMODE_DISABLE;
/* Restore UART configuration */
WRITE_REG(huart->Instance->CR1, tmpcr1);
huart->gState = HAL_UART_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(huart);
return HAL_OK;
}
```
分析一下UART初始化函数:
1. 初始化端口,波特率,字长为8B,停止位1位,定义为收发模式,没有硬件控制,16倍过采样数据,之后初始化端口
2. 设置TX的FIFO门限,设置RX的FIFO门限
3. 关闭FIFO模式
UART传输函数
```C++
/**
* @brief Send an amount of data in blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the sent data is handled as a set of u16. In this case, Size must indicate the number
* of u16 provided through pData.
* @note When FIFO mode is enabled, writing a data in the TDR register adds one
* data to the TXFIFO. Write operations to the TDR register are performed
* when TXFNF flag is set. From hardware perspective, TXFNF flag and
* TXE are mapped on the same bit-field.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* address of user data buffer containing data to be sent, should be aligned on a half word frontier (16 bits)
* (as sent data will be handled using u16 pointer cast). Depending on compilation chain,
* use of specific alignment compilation directives or pragmas might be required
* to ensure proper alignment for pData.
* @param huart UART handle.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be sent.
* @param Timeout Timeout duration.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
const uint8_t *pdata8bits;
const uint16_t *pdata16bits;
uint32_t tickstart;
/* Check that a Tx process is not already ongoing */
if (huart->gState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* In case of 9bits/No Parity transfer, pData buffer provided as input parameter
should be aligned on a u16 frontier, as data to be filled into TDR will be
handled through a u16 cast. */
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
if ((((uint32_t)pData) & 1U) != 0U)
{
return HAL_ERROR;
}
}
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_BUSY_TX;
/* Init tickstart for timeout management */
tickstart = HAL_GetTick();
huart->TxXferSize = Size;
huart->TxXferCount = Size;
/* In case of 9bits/No Parity transfer, pData needs to be handled as a uint16_t pointer */
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
pdata8bits = NULL;
pdata16bits = (const uint16_t *) pData;
}
else
{
pdata8bits = pData;
pdata16bits = NULL;
}
while (huart->TxXferCount > 0U)
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
{
huart->gState = HAL_UART_STATE_READY;
return HAL_TIMEOUT;
}
if (pdata8bits == NULL)
{
huart->Instance->TDR = (uint16_t)(*pdata16bits & 0x01FFU);
pdata16bits++;
}
else
{
huart->Instance->TDR = (uint8_t)(*pdata8bits & 0xFFU);
pdata8bits++;
}
huart->TxXferCount--;
}
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
{
huart->gState = HAL_UART_STATE_READY;
return HAL_TIMEOUT;
}
/* At end of Tx process, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
```
串口成功接收报文,这是串口助手的参数
![2_配置UART](/data/attachment/forum/202410/15/230534xp2nm0nmvz2jkzzm.png.thumb.jpg?rand=3701.7801121446946)
# 参考资料
[UART的FIFO功能](https://blog.csdn.net/wangwenxue1989/article/details/48289715)