本SDK使用的是RT-Threa4.1.1,所以像一些基本的框架,SDK已经为大家对接好了,我们只需要调用相关的函数即可!
我们在开发的过程中,会用到大量的RThread相关函数,所以推荐大家在使用过程中,进行学习,在开发的过程中解决问题,学习相关的函数!
驱动编写步骤
步骤一般这样的:
在SDK中编写代码
---> 进入menuconfig菜单进行相关的配置
---> 编译SDK
---> 烧录镜像
---> 验证功能
代码编译框架
整个SDK使用的是由Scons文件构建起来的编译框架!
SCons 是一套由 Python 语言编写的开源构建系统,类似于 GNU Make。它采用不同于通常 Makefile 文件的方式,而是使用 SConstruct 和 SConscript 文件来替代。这些文件也是 Python 脚本,能够使用标准的 Python 语法来编写。所以在 SConstruct、SConscript 文件中可以调用 Python 标准库进行各类复杂的处理,而不局限于 Makefile 设定的规则。
在 RT-Thread 的网站上可以找到详细的 SCons 用户手册。
如果你看完了RT-Thrad官网对于Scons的教程,还是没有理解!不用担心,我们在实践中学习,在下面我们会详细的说明Scons如何编写以及如何加入编译组中!
使用GPIO点亮一个LED
驱动编写准备
我们首先要确定自己能正确的编译SDK!并且将镜像烧录进开发板能够完美运行!这样就能排除很多的场外因素!!
创建用户代码文件夹
我事先在SDK中已经内置了一个LED驱动,这次我们将手动编写代码,实现LED的闪烁!
所以我们先要把\luban-lite\application\rt-thread\helloworld\user_led
这个文件夹中的文件全部删掉
!
使用VSCode打开SDK
我们使用VSCode打开SDK文件夹 luban-lite
新建文件
小知识
user_led.c
文件就是我们所需要的LED驱动,因为我们写的驱动很简单,也不需要什么复杂的操作,所以只需要一个c文件,并且在文件内将启动LED闪烁的函数设定为自启动即可上电LED自动闪烁!SConscript
文件的作用是将当前文件夹中的文件加入到编译组中,使当前的文件可以被编译。Kconfig
文件是menuconfig菜单的基本配置文件,菜单是根据menuconfig的配置进行生成的!
我们右键新建三个文件:user_led.c
SConscript
Kconfig
编写SConscript
在\luban-lite文件夹
中的SConscript文件位于这个树状结构的顶端,它充当了整个构建过程的入口点,并且会递归地搜索其下的子目录以查找更多的SConscript文件。这个过程可以描述如下:
上层的SConscript文件中编写的代码会包含对子目录的搜索指令。当SCons(构建工具)执行时,它会自动遍历这些子目录,寻找每个子目录中的SConscript文件。如果找到了SConscript文件,它将被执行,并且其内容将被整合到整个构建过程中。
所以我们在user_led文件夹中写入以下代码:
# 导入预定义的变量和函数
Import('AIC_ROOT') # 导入AIC_ROOT变量,通常用于表示项目根目录
Import('rtconfig') # 导入rtconfig变量,通常包含项目的配置信息
# 导入Python模块
import rtconfig # 导入rtconfig模块,以便直接使用其中的配置
from building import * # 从building模块导入所有内容,可能包含自定义的构建函数和变量
# 获取当前目录的路径
cwd = GetCurrentDir() # 获取当前SConscript文件所在的目录路径
# 设置头文件搜索路径
CPPPATH = [cwd] # 将当前目录添加到编译器的头文件搜索路径中
# 初始化源文件列表
src = [] # 创建一个空列表,用于存放源文件
# 如果在rtconfig中定义了USER_LED_ON,那么获取当前目录下所有的.c源文件,反之则不获取!
if GetDepend('USER_LED_ON'):
src = Glob(os.path.join(cwd, '*.c')) # 使用Glob函数查找当前目录下所有的.c文件,并将它们添加到src列表中
# 定义构建组
group = DefineGroup('lckfb-user-led', src, depend = [''], CPPPATH = CPPPATH)
# 创建一个名为'lckfb-user-led'的构建组,包含src列表中的所有源文件
# depend参数指定了构建这个组之前需要先构建的其他组或目标,这里为空字符串,表示没有依赖
# CPPPATH参数设置了头文件搜索路径,这里使用了前面定义的CPPPATH
# 返回构建组,供上层SConscript文件或其他SConscript文件使用
Return('group') # 将定义的构建组作为返回值,这样其他SConscript文件可以引用这个组
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
这是一个典型的SCons构建脚本,用于定义一个构建组,并设置相关的编译选项。通过使用Import函数,脚本可以访问在其他地方定义的变量和配置。Glob函数用于匹配特定模式的文件,并将它们作为源文件列表。
DefineGroup函数用于创建一个构建组,该组包含了源文件列表、依赖关系和编译器选项。
Return函数将构建组作为结果返回,以便它可以被其他构建脚本引用。
编写LED驱动
LED硬件说明
我们要控制用户LED,我们要首先查看原理图:
- 控制用户LED的引脚是那个?
- 用户LED是高电平触发还是低电平触发?
搞明白这两个问题就可以编写驱动程序了!
我们从原理图中可以看到
- LED连接的是
PE17
这个引脚! - 直接输出
高电平即可点亮LED
!低电平关闭LED
!
PIN设备介绍
我们编写GPIO驱动用到的是RT-Thread的PIN设备框架,调用的也是RT-Thread的函数进行编写。
RT-Thread 中的 PIN 设备指的是可以配置为输入或输出的GPIO引脚。
RT-Thread官方教程
引脚编号说明
在衡山派的SDK中GPIO是默认启动的,我们不需要开启时钟之类的,直接获取引脚编号,设定引脚的模式即可使用!
RT-Thread 提供的引脚编号
需要和芯片的引脚号
区分开来,它们并不是同一个概念,引脚编号
由 PIN 设备驱动程序定义
,和具体的芯片相关。
例如:
PE17
就是芯片的引脚号!PA3
就是芯片的引脚号!
PE17是芯片的引脚号
,而我们是根据 "PE.17" 这个字符串
进行搜索对应的PIN设备引脚编号
,记住这一点!
在RTThread中
芯片引脚号
和PIN设备引脚编号
一一对应,使用相关的rt_pin_get函数
即可根据引脚号
搜索到引脚编号
相关的应用:
/* rt_base_t是long类型typedef来的 */
rt_base_t user_led_pin = rt_pin_get("PE.17");
2
一般情况下都是使用一个算法从芯片引脚号字符串推算出一个PIN设备引脚编号! 我们调用
rt_pin_get函数
获取PIN设备引脚编号的时候实际上在调用\luban-lite\bsp\artinchip\hal\gpio\aic_hal_gpio.c文件
中的hal_gpio_name2pin函数
!
相关的调用逻辑是这样的:
rt_pin_get
---> struct rt_device_pin _hw_pin
中的.ops->pin_get函数
---> drv_pin_get函数
---> hal_gpio_name2pin函数
最终得到了PIN设备引脚编号!
程序线程说明
在RT-Thread中,线程可以看作是执行特定功能的代码片段,它有自己的执行上下文和优先级。
RT-Thread官方教程
线程可以处于以下几种状态之一
初始状态
当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。就绪状态
在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。运行状态
线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。挂起状态
也称阻塞态
。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。关闭状态
当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。
线程之间的切换由操作系统根据线程的优先级和调度策略来管理。
在编写一个LED闪烁的程序时,线程将扮演关键角色。以下是线程与LED闪烁之间的关系:
独立执行
: 可以创建一个专门的线程来控制LED的闪烁,这样即使其他线程正在执行其他任务,LED的闪烁也不会受到影响。周期性操作
:线程可以设置为周期性地执行,比如每隔一定时间切换LED的状态(从亮到灭,或从灭到亮),从而实现闪烁效果。实时性
:由于RT-Thread是实时操作系统,它可以保证线程在指定的时间内响应,这对于精确控制LED闪烁的频率和持续时间非常重要。
驱动程序编写
我们在user_led.c文件
中编写以下代码:
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 文档网站:wiki.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 嘉立创社区问答:https://www.jlc-bbs.com/lckfb
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <rtthread.h>
#include "rtdevice.h"
#include "aic_core.h"
#include "aic_hal_gpio.h"
#define THREAD_PRIORITY 25 // 线程优先级,数字越大优先级越高
#define THREAD_STACK_SIZE 512 // 线程堆栈大小,决定了线程可以使用的内存空间
#define THREAD_TIMESLICE 5 // 线程时间片,决定了线程在调度时能占用CPU的最长时间
static rt_thread_t led_thread = RT_NULL; // 声明线程控制块指针,用于后续创建和管理线程
static rt_base_t user_led_pin = RT_NULL; // 声明引脚编号变量,初始化为NULL,用于存储LED的引脚编号
// 线程入口函数,这是线程启动后执行的函数
static void user_led_thread_entry(void *param)
{
while(1) // 无限循环,使线程持续运行
{
rt_pin_write(user_led_pin, PIN_HIGH); // 将LED引脚电平设置为高
rt_thread_mdelay(100); // 线程延时100毫秒,让LED保持高电平一段时间
rt_pin_write(user_led_pin, PIN_LOW); // 将LED引脚电平设置为低
rt_thread_mdelay(100); // 线程延时100毫秒,让LED保持低电平一段时间
}
}
// 初始化LED的函数,在系统启动时调用
static void usr_led_run(int argc, char **argv)
{
user_led_pin = rt_pin_get("PE.17"); // 获取名为"PE.17"的引脚编号
if ((user_led_pin == -RT_EINVAL) || (user_led_pin == -RT_ENOSYS)) // 如果获取失败,打印错误信息并返回
{
rt_kprintf("Failed to get the pin PE.17\n");
return; // 获取引脚失败,直接返回,避免后续操作导致错误
}
rt_pin_mode(user_led_pin, PIN_MODE_OUTPUT); // 设置引脚为输出模式
rt_pin_write(user_led_pin, PIN_LOW); // 初始时将LED设置为低电平
/* 创建线程,名称是 led_thread,入口是 user_led_thread_entry */
led_thread = rt_thread_create("led_thread", // 线程名称
user_led_thread_entry, RT_NULL, // 线程入口函数和参数
THREAD_STACK_SIZE, // 线程堆栈大小
THREAD_PRIORITY, // 线程优先级
THREAD_TIMESLICE); // 线程时间片
if (led_thread == RT_NULL) // 如果线程创建失败,打印错误信息并返回
{
rt_kprintf("Failed to create the led_thread\n");
return; // 线程创建失败,直接返回,防止对无效的线程进行操作
}
/* 如果获得线程控制块,启动这个线程 */
rt_thread_startup(led_thread); // 启动线程,使其开始执行
}
// 导出函数自动运行,在系统初始化时调用usr_led_run函数
INIT_APP_EXPORT(usr_led_run);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
关于的函数自动初始化说明
初始化顺序数值越小启动的越早!
初始化顺序 | 宏接口 | 描述 |
---|---|---|
1 | INIT_BOARD_EXPORT(fn) | 非常早期的初始化,此时调度器还未启动 |
2 | INIT_PREV_EXPORT(fn) | 主要是用于纯软件的初始化、没有太多依赖的函数 |
3 | INIT_DEVICE_EXPORT(fn) | 外设驱动初始化相关,比如网卡设备 |
4 | INIT_COMPONENT_EXPORT(fn) | 组件初始化,比如文件系统或者 LWIP |
5 | INIT_ENV_EXPORT(fn) | 系统环境初始化,比如挂载文件系统 |
6 | INIT_APP_EXPORT(fn) | 应用初始化,比如 GUI 应用 |
提醒
如果不想使用自动初始化函数也可以使用以下的宏定义,将函数和自定义命令连接起来,可以在命令行窗口启动对应的初始化函数使其运行!
- MSH_CMD_EXPORT(command, desc)
command
要导出的命令desc
导出命令的描述
例如:
usr_led_run 为函数名!
MSH_CMD_EXPORT(usr_led_run, Enable user LED flashing)
编写Kconfig
Kconfig 源于 Linux内核的配置构建系统。
RT-Thread官方教程
C语言项目的裁剪配置本质上通过条件编译和宏的展开来实现的,RT-Thread借助Kconfig这套机制更方便的实现了这一功能。当前以Windows下Env工具中的使用为例,简述Kconfig在RT-Thread的工作机制。
Kconfig机制包括了Kconfig文件和配置UI界面(如menuconfig,pyconfig等)。
Kconfig机制特点:
Kconfig文件中的配置项会映射至rtconfig.h中
Kconfig文件可以随源码分散至各级子目录,便于灵活修改。
在\luban-lite\application\rt-thread\helloworld\user_led\Kconfig文件
中编写如下代码:
config USER_LED_ON
bool "Turn on the user LED flashing"
default n
help
More information is available at: https://wiki.lckfb.com/
2
3
4
5
重要说明!!
在menuconfig菜单中开启该选项!则USER_LED_ON
这个字段将会被工具链自动的写入\luban-lite\rtconfig.h文件
中,我们就可以根据rtconfig.h文件
中是否定义了USER_LED_ON
这个字段来进行条件编译
!
解释
config USER_LED_ON
: 这一行定义了一个配置项,名为USER_LED_ON。在配置界面中,这将显示为一个选项。bool "Turn on the user LED flashing"
: 这一行指定了配置项的类型为布尔值(bool),即它只能有两个状态:启用(y)或禁用(n)。引号中的文字是用户在配置界面中看到的选项描述。default n
: 这一行设置了该配置项的默认值。在这个例子中,默认值是n,意味着如果不进行手动配置,这个选项将是禁用状态。help
: 这一行开始了一个帮助部分,用于提供关于这个配置选项的额外信息。用户在配置界面中选择这个选项并请求帮助时,将看到这部分内容。More information is available at: https://wiki.lckfb.com/
: 这是帮助文本的内容,告诉用户如果需要更多关于这个配置选项的信息,可以访问提供的网址。
为了确保我们的Kconfig文件中的配置项能够正确地映射到rtconfig.h文件中
,我们需要保持当前目录的Kconfig文件与上级目录中的Kconfig文件的联系。我们需要在上级目录的Kconfig文件
中添加当前Kconfig文件的路径
。
这样,当我们进行配置时,上级目录的Kconfig文件就能够包含并引用当前目录的配置项,从而确保配置的一致性和正确性。
所以我们打开\luban-lite\application\rt-thread\helloworld\Kconfig
这个文件,在其中添加以下语句:
source "application/rt-thread/helloworld/user_led/Kconfig"
这样就能保持正常的映射!
进入menuconfig配置
我们 双击 luban-lite文件夹
下的 win_env.bat
脚本打开env工具:
输入以下命令列出所有板级配置:
list
选择 d13x_JLC_rt-thread_helloworld
这个配置!这个是我们衡山派开发板的默认配置!输入以下命令即可:
lunch 3
输入以下命令进入 menuconfig菜单 :
scons --menuconfig
选中 Turn on the user LED flashing
保存退出
按
Y
选中 按N
取消选中 方向键左右
调整 最下面菜单的选项 方向键上下
调整 列表的选项
这时候我们可以打开 \luban-lite\rtconfig.h
文件查看我们在 Kconfig文件
中设定的语句。
可以看到在 \luban-lite\rtconfig.h
文件中已经定义了 USER_LED_ON
这个字段:
联想到我们之前编写的 \luban-lite\application\rt-thread\helloworld\user_led\SConscript
文件,我们在此文件中有一句代码:
# 如果在rtconfig中定义了USER_LED_ON,那么获取当前目录下所有的.c源文件,反之则不获取!
if GetDepend('USER_LED_ON'):
src = Glob(os.path.join(cwd, '*.c')) # 使用Glob函数查找当前目录下所有的.c文件,并将它们添加到src列表中
2
3
其中USER_LED_ON
起到了条件编译的作用!也就是定义了USER_LED_ON
就编译该目录下的所有的源代码,反之则不编译!
所以相关的结构就已经非常清楚了!
Kconfig文件
控制着menuconfig菜单的选项,直接关联着\luban-lite\rtconfig.h
文件中相关定义的增加和删减!SConscript文件
控制着是否将源文件加入编译,可以引入rtconfig变量
进行搜索和检查\luban-lite\rtconfig.h
文件中相关定义,进行各种操作!
相关逻辑如下
用户进入menuconfig菜单进行配置相关的选项并且保存修改
\luban-lite\rtconfig.h 文件中将会出现所对应的相关定义
SConscript文件将会根据rtconfig.h里面的定义进行判断是否加入源文件编译
在SDK编译的时候则会自动检查SConscript文件是否有能够加入编译的源文件
编译SDK
接下来我们在Env窗口输入以下命令编译SDK:
详细资料请查看 SDK编译 (跳转🚀)
scons -j16
说明
-j
用来选择参与编译的核心数: 我这里是选择16
大家可以根据自己的电脑来选择
核心越多编译越快
如果写的数量高于电脑本身,那么就自动按照最高可用的来运行!
编译完成之后镜像位置:
烧录镜像与验证
详细教程请查看 镜像烧录 (跳转🚀)
烧录完镜像之后就可以看到绿色的LED灯在闪烁!