本SDK使用的是RT-Threa4.1.1,所以像一些基本的框架,SDK已经为大家对接好了,我们只需要调用相关的函数即可!
我们在开发的过程中,会用到大量的RThread相关函数,所以推荐大家在使用过程中,进行学习,在开发的过程中解决问题,学习相关的函数!
UART实验说明
我们使用USB-TTL模块连接PA.6
和PA.7
两个引脚,将引脚复用为UART3
的功能,实现串口双向的收发不定长数据!
并且,通过 Debug调试串口 发送自定义数据
和显示接收的数据
!!
驱动编写步骤
步骤一般这样的:
在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如何编写以及如何加入编译组中!
使用UART接收与发送数据
驱动编写准备
我们首先要确定自己能正确的编译SDK!并且将镜像烧录进开发板能够完美运行!这样就能排除很多的场外因素!!
创建相关代码文件夹
我们先要在\luban-lite\application\rt-thread\helloworld\
这个文件夹中创建一个user_uart3文件夹
!
使用VSCode打开SDK
我们使用VSCode打开SDK文件夹 luban-lite
新建文件
小知识
user_uart3.c
文件就是我们所需要的驱动,我们在文件夹中实现相关的函数接口!创建相关的线程进行接收和发送!SConscript
文件的作用是将当前文件夹中的文件加入到编译组中,使当前的文件可以被编译。Kconfig
文件是menuconfig菜单的基本配置文件,菜单是根据menuconfig的配置进行生成的!
我们右键新建三个文件:user_uart3.c
SConscript
Kconfig
编写SConscript
在\luban-lite文件夹
中的SConscript文件位于这个树状结构的顶端,它充当了整个构建过程的入口点,并且会递归地搜索其下的子目录以查找更多的SConscript文件。这个过程可以描述如下:
上层的SConscript文件中编写的代码会包含对子目录的搜索指令。当SCons(构建工具)执行时,它会自动遍历这些子目录,寻找每个子目录中的SConscript文件。如果找到了SConscript文件,它将被执行,并且其内容将被整合到整个构建过程中。
所以我们在user_uart3文件夹
中的SConscript文件
写入以下代码:
# 导入预定义的变量和函数
Import('AIC_ROOT') # 导入AIC_ROOT变量,通常用于表示项目根目录
Import('rtconfig') # 导入rtconfig变量,通常包含项目的配置信息
# 导入Python模块
import rtconfig # 导入rtconfig模块,以便直接使用其中的配置
from building import * # 从building模块导入所有内容,可能包含自定义的构建函数和变量
# 获取当前目录的路径
cwd = GetCurrentDir() # 获取当前SConscript文件所在的目录路径
# 设置头文件搜索路径
CPPPATH = [cwd] # 将当前目录添加到编译器的头文件搜索路径中
# 初始化源文件列表
src = [] # 创建一个空列表,用于存放源文件
# 如果在rtconfig中定义了 USER_UART3_TEST_ON 和 AIC_USING_UART3 ,那么获取当前目录下所有的.c源文件,反之则不获取!
if GetDepend('USER_UART3_TEST_ON') and GetDepend('AIC_USING_UART3'):
src = Glob(os.path.join(cwd, '*.c')) # 使用Glob函数查找当前目录下所有的.c文件,并将它们添加到src列表中
# 定义构建组
group = DefineGroup('lckfb-user-uart3-test', src, depend = [''], CPPPATH = CPPPATH)
# 创建一个名为'lckfb-user-uart3-test'的构建组,包含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函数将构建组作为结果返回,以便它可以被其他构建脚本引用。
编写UART3驱动
硬件连接说明
该引脚复用分配图可以在我们的百度网盘资料的硬件资料中找到!
UART设备介绍
UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器,UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。是在应用程序开发过程中使用频率最高的数据总线。
UART 串口的特点是将数据一位一位地顺序传送,只要 2 根传输线就可以实现双向通信,一根线发送数据的同时用另一根线接收数据。UART 串口通信有几个重要的参数,分别是波特率、起始位、数据位、停止位和奇偶检验位,对于两个使用 UART 串口通信的端口,这些参数必须匹配,否则通信将无法正常完成。UART 串口传输的数据格式如下图所示:
参数说明
起始位
:表示数据传输的开始,电平逻辑为 “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官方教程
接口对接说明
在衡山派的SDK中已经对各种外设的资源进行了对接
,将这些外设都已经抽象成了设备
,例如uart外设就抽象成了 "uart1
"、"uart2
"......等
我们只需要查看相关的的文档并在menuconfig菜单中开启相关的外设,那么在开发板的命令行中输入list_device
命令即可看到相关的设备运行!
关于uart相关的配置在RTOS-SDK中的外设接口目录中:Uart参数配置
我们可以先不用看RTOS-SDK中的Uart参数配置,先跟着教程走一遍流程这样就好的多!
我们选定的是PA.6
和PA.7
作为串口3的复用引脚,那么我们在哪里查看相关的引脚复用信息呢?
引脚的复用在我们的配置文件中存在着,我们打开 \luban-lite\target\d13x\JLC\pinmux.c
文件,里面就是引脚复用的定义。
pinmux.c文件说明
struct aic_pinmux
{
unsigned char func; // 用数字表示复用功能,可以查看芯片手册中的引脚复用功能说明。
unsigned char bias; // 标识上下拉的是否的开启,使用定义进行设置。
unsigned char drive; // 用数字表示驱动强度,默认即可
char * name; // 表示引脚名称,字符串,例如 "PA.1"
};
2
3
4
5
6
7
我们在第37行
可以看到,相关的条件编译代码:
#ifdef AIC_USING_UART3
/* uart3 */
{5, PIN_PULL_DIS, 3, "PA.6"}, // BT_UART3_TX
{5, PIN_PULL_DIS, 3, "PA.7"}, // BT_UART3_RX
#endif
2
3
4
5
如果 AIC_USING_UART3
被定义,则会定义下面的PA.6
和PA.7
的uart3功能复用
!否则这两个引脚将不会被复用为UART3!
所以,在这个pinmux.c文件
中定义了全部的引脚复用功能
,SDK会忠实的按照这个文件的定义来进行复用
,而不会让我们在代码中指定!这点尤其要注意!!
复用冲突检查
当我们在使用PA.6
和PA.7
引脚作为UART3
功能复用引脚的时候,会有一种情况:
- 有另外的一个功能占用了
PA.6
或者PA.7
的引脚!!这时候就会导致该功能的引脚无法使用
,但也不会报错
,所以为了避免这样的情况,我们需要进行引脚复用冲突检查!!
我们打开 \luban-lite\target\d13x\JLC\pinmux.c
文件,里面就是引脚复用的定义。
注意
我们查看pinmux.c文件的主要作用是为了防止有别的功能占用了我们将要使用的引脚,导致出现问题。
我们将这些 占用引脚的功能宏定义
记录下来,在后面的 menuconfig配置 步骤中全部取消掉!
这样我们就不会出现引脚冲突的问题!
然后按下 Ctrl + F
整体搜索PA.6
和PA.7
引脚,记录下来有这两个引脚的条件宏定义名称!
如下图,我们就可以记住 AIC_USING_PSADC6
这个字段,到时候在 menuconfig菜单中
关闭对应的功能:
TIP
在VSCode中代码提示和高亮是非常方便的,我们可以很容易的就看到 暗淡的是没有被启用的
, 正常颜色是被启用的
!
经过搜寻一共找到下面的定义:
AIC_USING_PSADC6
AIC_USING_GPAI6
AIC_USING_PSADC7
AIC_USING_GPAI7
2
3
4
5
我们需要将上面四个都找到,然后关闭该功能的复用,确保UART3在复用的时候PA.6
和PA.7
不会被占用!
程序线程说明
在RT-Thread中,线程可以看作是执行特定功能的代码片段,它有自己的执行上下文和优先级。
RT-Thread官方教程
线程可以处于以下几种状态之一
初始状态
当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。就绪状态
在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。运行状态
线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。挂起状态
也称阻塞态
。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。关闭状态
当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。
线程之间的切换由操作系统根据线程的优先级和调度策略来管理。
在编写一个UART的串口接收发送程序时,线程扮演了什么样的角色呢?
独立执行
串口接收线程:通过创建一个独立的线程(serial_recv_thread_entry),该线程专门处理串口数据的接收。这样,即使主线程正在执行其他任务,串口数据的接收和处理也能独立进行,不会互相影响。
周期性操作
主线程周期性发送数据:在serial3_thread_entry中,使用一个无限循环定时发送数据。通过rt_thread_mdelay(1000)实现每隔1秒发送一次数据,达到周期性发送的效果。这种方式可以让串口持续进行数据的交互。
实时性
RT-Thread实时特性:RT-Thread作为实时操作系统,确保线程在指定的时间内得到响应。接收线程可以及时响应串口数据的到达,当有数据时会触发中断,立即释放信号量,使主线程能够快速读取数据。这种高效的实时性保证了数据的即时处理和反馈,适合对实时性要求较高的串口通信。
数据流管理
缓冲区与标志位:串口接收线程负责将接收到的数据存入缓冲区,并通过标志位serial_recv_flag通知主线程数据已准备好。主线程可以根据标志位的状态决定是否读取数据,确保数据管理的有效性。
错误处理
发送和接收的可靠性:在发送数据时,函数Serial_Send_Byte和Serial_Send_String都有错误检查机制,确保数据发送的可靠性。这使得即使在复杂的多线程环境中,数据传输仍然能够保持稳定和准确。
驱动程序编写
我们在user_uart3.c文件
中编写以下代码:
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 文档网站:wiki.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 嘉立创社区问答:https://www.jlc-bbs.com/lckfb
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*/
#include <getopt.h>
#include <string.h>
#include <rtthread.h>
#include <aic_core.h>
#include <stdlib.h>
#include <sys/time.h>
#include "hal_adcim.h"
#include "rtdevice.h"
#include "aic_log.h"
#include "hal_gpai.h"
#include <stdio.h>
#include "aic_hal_gpio.h"
#define SAMPLE_UART_NAME "uart3" // 串口设备名称
#define RCV_BUFF_SIZE_MAX 1024 // 接收最大字节长度
static struct rt_semaphore rx_sem; // 用于接收消息的信号量
static rt_device_t serial; // 串口设备句柄
static rt_thread_t serial_recv_thread; // 串口接收线程句柄
static char serial_recv_buff[RCV_BUFF_SIZE_MAX]; // 串口接收缓存区
static char serial_recv_flag; // 串口接收标志
static int serial_recv_length; // 接收字节长度
/* ====================串口发送和打印线程=================== */
#define THREAD_PRIORITY 25 // 线程优先级
#define THREAD_STACK_SIZE 4096 // 线程大小
#define THREAD_TIMESLICE 20 // 时间片
static rt_thread_t serial_thread = RT_NULL; // 线程控制块
// 中断接收回调函数
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
/* 串口有数据传入后产生中断,调用此回调函数,释放信号量 */
if (size > 0)
rt_sem_release(&rx_sem);
return RT_EOK;
}
// 串口接收线程入口函数
static void serial_recv_thread_entry(void *param)
{
rt_kprintf("\nserial_recv_thread_entry run ......\n");
while(1)
{
char temp_recv_buff = 0; // 接收临时缓存区
int ret = rt_device_read(serial, 0, &temp_recv_buff, 1);
if(ret < 0) // 出现了错误
{
pr_debug("read() return [%ld] %s\n", rt_get_errno(), rt_strerror(rt_get_errno()));
}
if(ret == 0) // 未接到数据
{
// 重置信号量
rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL);
// 获取信号量,如果没有获取得到则阻塞在这里永远等待。
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
if(ret == 1) // 接收到1字节的数据
{
// 防止数据超出缓存区的大小
if(serial_recv_length < RCV_BUFF_SIZE_MAX - 1)
{
// 存入接收缓存区并递增长度
serial_recv_buff[serial_recv_length++] = temp_recv_buff;
// rt_kprintf("%x\n", temp_recv_buff); // 打印接收到的字节,用于调试
}
else
{
// 如果缓冲区已满,则从0开始覆盖旧数据
serial_recv_length = 0;
serial_recv_buff[serial_recv_length++] = temp_recv_buff;
}
// 为接收缓存区最后添加 '\0'
serial_recv_buff[serial_recv_length] = '\0';
// 设置串口接收完成标志
serial_recv_flag = 1;
}
}
}
/************************************************
函数名称 : Clear_recv_buff
功 能 : 清空串口接收缓存区
参 数 : 无
返 回 值 :
作 者 : LC
*************************************************/
static void Clear_recv_buff(void)
{
// 清空接收缓存区
rt_memset(serial_recv_buff, 0, sizeof(serial_recv_buff));
// 清空标志位
serial_recv_flag = 0;
// 清空缓存区长度计量
serial_recv_length = 0;
}
/************************************************
函数名称 : serial_send_byte
功 能 : 串口发送一个字节
参 数 : 发送的数据
返 回 值 : RT_EOK成功 -RT_ERROR失败
作 者 : LC
*************************************************/
static int Serial_Send_Byte(uint8_t dat)
{
int ret = rt_device_write(serial, 0, &dat, 1);
if(ret != 1)
{
LOG_E("Failed to [Serial_Send_Byte] code[%d] !!!", ret);
return -RT_ERROR;
}
return RT_EOK;
}
/************************************************
函数名称 : Serial_Send_String
功 能 : 串口发送字符串
参 数 : data_buff缓存区地址
返 回 值 : RT_EOK成功 -RT_ERROR失败
作 者 : LCKFB
*************************************************/
static int Serial_Send_String(uint8_t *data_buff)
{
int err_count = 0;
/* 地址为空 或者 值为空 跳出 */
while(data_buff && *data_buff)
{
if(RT_EOK != Serial_Send_Byte(*data_buff++))
{
err_count++;
continue;
}
}
/* 如果err_count不为0,则说明发送的时候有错误!!! */
if(err_count)
{
LOG_E("serial_send_string failed !!!");
return -RT_ERROR;
}
return RT_EOK;
}
/******************************************************************
* 函 数 名 称:Serial_Recv_DATA
* 函 数 说 明:接串口的数据
* 函 数 形 参:data_buff数据缓存区
* 函 数 返 回: 0: 未接收到数据
* 其他: 接收到的数据长度
* 作 者:LCKFB
* 备 注:无
******************************************************************/
int Serial_Recv_DATA(uint8_t *data_buff)
{
int i;
/* 判断是否接到了数据 */
if((serial_recv_flag != 1) || (serial_recv_length == 0))
{
/* 未接到 */
return 0;
}
/* 将数据转存到指针指向的地址中 */
for(i = 0; i < serial_recv_length; i++)
{
data_buff[i] = serial_recv_buff[i];
}
/* 加入字符串结尾 */
data_buff[i] = '\0';
/* 清除接收的数据、标志位和数据长度。 */
Clear_recv_buff();
return i; // 返回接收到的数据长度
}
/************************************************
函数名称 : UART_Init
功 能 : 串口初始化
参 数 : 无
返 回 值 : RT_EOK成功 -RT_ERROR失败
作 者 : LCKFB
*************************************************/
static int UART_Init(void)
{
int ret = 0;
// 清空接收缓存区
rt_memset(serial_recv_buff,0,sizeof(serial_recv_buff));
// 清空标志位
serial_recv_flag = 0;
// 清空缓存区长度计量
serial_recv_length = 0;
rt_kprintf("Try to open(%s)\n", SAMPLE_UART_NAME);
// 获取串口句柄
serial = rt_device_find(SAMPLE_UART_NAME);
if (!serial)
{
LOG_E("find %s failed!\n", SAMPLE_UART_NAME);
return -RT_ERROR;
}
// 初始化信号量
ret = rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
if (ret != RT_EOK)
{
LOG_E("failed to rt_sem_init !\n");
return -RT_ERROR;
}
// 打开串口设备
ret = rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
if (ret != RT_EOK)
{
LOG_E("open %s failed : %d !\n", SAMPLE_UART_NAME, ret);
return -RT_ERROR;
}
// 设置接收回调函数
rt_device_set_rx_indicate(serial, uart_input);
// 创建串口数据接收线程
serial_recv_thread = rt_thread_create("serial", serial_recv_thread_entry, RT_NULL, 1024*2, 15, 20);
if (serial_recv_thread != RT_NULL)
{
// 启动线程
rt_thread_startup(serial_recv_thread);
}
else
{
rt_device_close(serial);
LOG_E("Failed to [rt_thread_create] !!!");
return -RT_ERROR;
}
return RT_EOK;
}
// 线程入口函数
static void serial3_thread_entry(void *param)
{
rt_kprintf("Start serial3_thread_entry...\n");
while(1)
{
int count = 0;
/* 接收缓存区 */
uint8_t recv_buff[128] = {0};
/* 获取接收到的数据长度 */
count = Serial_Recv_DATA(recv_buff);
/* 确保 count 不超过 recv_buff 大小,避免越界访问 */
if (count > sizeof(recv_buff))
{
LOG_E("Error: Received data exceeds buffer size! count = %d",count);
count = sizeof(recv_buff); // 限制数据长度避免溢出
}
if (count > 0)
{
rt_kprintf("\n======================================\n");
rt_kprintf("\nRead Data = %s\n", recv_buff);
rt_kprintf("\n======================================\n");
}
/* 延迟 1000 毫秒 */
rt_thread_mdelay(1000);
}
}
// 数据发送函数
static void send_demoData(int argc, char **argv)
{
static int num = 1;
uint8_t Send_Buff[128] = {"立创·衡山派D133EBS开发板 * UART框架使用测试"};
int ret = 0;
char buffer[128] = {0};
// 使用 snprintf 来格式化要发送的字符串
snprintf(buffer, sizeof(buffer), "【%d】%s", num, (argc == 2) ? *(argv+1) : (char *)Send_Buff);
// 发送数据
ret = Serial_Send_String((uint8_t *)buffer);
if(ret != RT_EOK)
{
LOG_E("%s: The test data transmission failed.", __FUNCTION__);
}
else
{
rt_kprintf("\n[%d] Send success\n", num);
num++; // 只有发送成功时才递增 num
}
}
// 导出函数为命令
MSH_CMD_EXPORT(send_demoData, Send test data);
// 串口接收和发送线程开启
static void uart3_test_on(int argc, char **argv)
{
int ret = UART_Init(); // 串口初始化
if(ret != RT_EOK)
{
LOG_E("Failed to [UART_Init] !!!");
LOG_E("file: %s line: %d", __FILE__, __LINE__);
return;
}
rt_kprintf("UART_Init run END!!\n");
/* 创建线程,名称是 serial3_thread,入口是 serial3_thread_entry */
serial_thread = rt_thread_create("serial3_thread",
serial3_thread_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (serial_thread != RT_NULL)
rt_thread_startup(serial_thread);
rt_kprintf("Test transmission and reception using UART3 serial port!!\n");
}
// 导出函数为命令
MSH_CMD_EXPORT(uart3_test_on, Test transmission and reception using UART3 serial port);
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
导出自己的命令到MSH命令列表
将函数和自定义命令连接起来,可以在命令行窗口启动对应的初始化函数使其运行!
- MSH_CMD_EXPORT(command, desc)
command
要导出的命令desc
导出命令的描述
例如:
uart3_test_on 为函数名!
MSH_CMD_EXPORT(uart3_test_on, Test transmission and reception using UART3 serial port);
void send_demoData(int argc, char **argv) 函数特别讲解
这个函数原型是典型的 C 语言命令行参数处理方式,类似于 main 函数的参数。下面是每个参数的详细解释:
int argc
:这是一个整数,代表传入函数的参数数量(argument count)。argc 至少为 1,因为第一个参数(argv[0])通常是程序的名称。char **argv
:这是一个字符指针的指针(也称为字符串数组),它指向传入函数的参数值(argument vector)。每个元素是一个指向字符串的指针,代表一个命令行参数。
下面是 argv
的详细内容:
argv[0]
:通常是程序的名称或命令名。argv[1]
到argv[argc-1]
:包含了命令行上的实际参数。
例子说明:
假设我们在命令行中输入以下命令:send_demoData HelloWorld!
在这个例子中:
argc
将被设置为 2,因为总共有两个参数:命令名 send_demoData 和字符串 "HelloWorld!"。argv
将是一个包含两个字符串指针的数组:argv[0]
将包含字符串 "send_demoData"。argv[1]
将包含字符串 "HelloWorld!"。
在 send_demoData 函数内部,可以使用这些参数来执行不同的操作。例如,函数中有一个检查 argc 的分支,如果 argc 等于 2(意味着用户提供了一个额外的参数),它将发送 argv[1] 指向的字符串;否则,它将发送一个预设的字符串。
编写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_uart3\Kconfig文件
中编写如下代码:
config USER_UART3_TEST_ON
bool "Test transmission and reception using UART3 serial port"
default n
help
More information is available at: https://wiki.lckfb.com/
2
3
4
5
重要说明!!
- 在menuconfig菜单中开启该选项!则
USER_UART3_TEST_ON
这个字段将会被工具链自动的写入\luban-lite\rtconfig.h文件
中,我们就可以根据rtconfig.h文件
中是否定义了USER_UART3_TEST_ON
这个字段来进行条件编译
!
解释
config USER_UART3_TEST_ON
:定义一个名为 USER_UART3_TEST_ON 的配置选项。这个选项通常用来控制是否启用与 UART3 串口相关的测试功能。bool "Test transmission and reception using UART3 serial port"
:bool 表示这个配置选项是一个布尔值(true/false)。用户可以选择启用(y)或禁用(n)这个选项。
"Test transmission and reception using UART3 serial port" 这是用户界面中显示的描述信息。当用户在配置界面(如 menuconfig)中浏览时,会看到这一描述,以便了解这个选项的作用。
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_uart3/Kconfig"
这样就能保持正常的映射!
进入menuconfig配置
我们 双击 luban-lite文件夹
下的 win_env.bat
脚本打开env工具:
输入以下命令列出所有板级配置:
list
选择 d13x_JLC_rt-thread_helloworld
这个配置!这个是我们衡山派开发板的默认配置!输入以下命令即可:
lunch 3
输入以下命令进入 menuconfig菜单 :
scons --menuconfig
开始进行各种选项的配置
按
Y
选中按
N
取消选中
回车
进入该目录方向键
左右
调整 最下面菜单的选项方向键
上下
调整 列表的选项
将menuconfig菜单中下面的选项设定为:
[*] Test transmission and reception using UART3 serial port
Board options --->
[*] Using Uart3
UART3 Parameter --->
(48000000) UART3 clk frequence
(115200) UART3 baudrate
(8) UART3 data bits
(1) UART3 stop bits
(0) UART3 parity (0=none, 1=odd, 2=even)
UART3 protocol (RS232) --->
(X) RS232
( ) RS485
UART3 mode (RS232 normal) --->
(X) RS232 normal
( ) RS232 auto flow control
( ) RS232 unauto flow control
( ) RS232 software flow control
( ) RS232 software and hardware flow control
[ ] Enable UART3 dma mode
[*] Using GPAI
GPAI options --->
[ ] Using GPAI6
[ ] Using GPAI7
[ ] Using PSADC
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
我们为什么要取消GPAI6、GPAI7、PSADC6和PSADC7呢?
具体是为了防止引脚复用冲突!在上面 【
复用冲突检查
】 章节中我已经详细说明了原由!
这时候我们可以打开 \luban-lite\rtconfig.h
文件查看我们在 Kconfig文件
中设定的语句。
可以看到在 \luban-lite\rtconfig.h
文件中已经定义了 USER_UART3_TEST_ON
和 AIC_USING_UART3
字段:
联想到我们之前编写的 \luban-lite\application\rt-thread\helloworld\user_led\SConscript
文件,我们在此文件中有一句代码:
# 如果在rtconfig中定义了 USER_UART3_TEST_ON 和 AIC_USING_UART3 ,那么获取当前目录下所有的.c源文件,反之则不获取!
if GetDepend('USER_UART3_TEST_ON') and GetDepend('AIC_USING_UART3'):
src = Glob(os.path.join(cwd, '*.c')) # 使用Glob函数查找当前目录下所有的.c文件,并将它们添加到src列表中
2
3
其中 USER_UART3_TEST_ON
和 AIC_USING_UART3
起到了条件编译的作用!也就是定义了 USER_UART3_TEST_ON
和 AIC_USING_UART3
就编译该目录下的所有的源代码,反之则不编译!
所以相关的结构就已经非常清楚了!
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
大家可以根据自己的电脑来选择
核心越多编译越快
如果写的数量高于电脑本身,那么就自动按照最高可用的来运行!
编译完成之后镜像位置:
烧录镜像与验证
详细教程请查看 镜像烧录 (跳转🚀)
我们详细看下面的现象:
输入
uart3_test_on
启动刚刚我们编写的驱动输入
send_demoData hello world!
用来发送 "hello world!" 这一语句!这个语句也可以替换成其他的。