添加串口设备
我手头的开发版是 ART-Pi 电路上引出了两排排针,其中有一个 USART1
串口,电路图如下:
CubeMX 配置
接下来我们打开示例代码 sdk-bsp-stm32h750-realthread-artpi\projects\art_pi_blink_led
中的 board
目录中的 CubeMX 进行配置。
这里需要注意的是有些功能的管脚是共用的,如果我们在 Categories 中点击选择,会默认选择管脚,但是实际上和我们所使用的不一致,这种情况要先在 Pinout view 上面点击选择对应管脚设置再打开。
配置完后在 Configuration 的 Parameter Settings 中设置一些串口基础东西,例如 波特率、校验位、停止位等。
然后 GENERATE CODE 生成代码,生成后我们还需要打开两个文件进行配置。
Kconfig 配置
由于工程中的 kconfig 的路径不对,所以不能使用 menuconfig
来配置,那就先不管这个了,打开 kconfig.h
文件配置是一样的。
board.h 文件中新增:
1
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
| /*-------------------------- UART CONFIG BEGIN --------------------------*/
/** After configuring corresponding UART or UART DMA, you can use it.
*
* STEP 1, define macro define related to the serial port opening based on the serial port number
* such as #define BSP_USING_UATR1
*
* STEP 2, according to the corresponding pin of serial port, define the related serial port information macro
* such as #define BSP_UART1_TX_PIN "PA9"
* #define BSP_UART1_RX_PIN "PA10"
*
* STEP 3, if you want using SERIAL DMA, you must open it in the RT-Thread Settings.
* RT-Thread Setting -> Components -> Device Drivers -> Serial Device Drivers -> Enable Serial DMA Mode
*
* STEP 4, according to serial port number to define serial port tx/rx DMA function in the board.h file
* such as #define BSP_UART1_RX_USING_DMA
*
*/
#ifdef BSP_USING_UART1
#define BSP_UART1_TX_PIN "PA9"
#define BSP_UART1_RX_PIN "PA10"
#endif
#ifdef BSP_USING_UART4
#define BSP_UART4_TX_PIN "PA0"
#define BSP_UART4_RX_PIN "PI9"
#endif
#ifdef BSP_USING_UART6
#define BSP_UART6_TX_PIN "PC6"
#define BSP_UART6_RX_PIN "PC7"
#endif
/*-------------------------- UART CONFIG END --------------------------*/
|
rtconfig.h 中新增:
1
2
3
4
5
6
7
| /* On-chip Peripheral */
#define BSP_USING_GPIO
#define BSP_USING_UART
#define BSP_USING_UART4
#define BSP_USING_UART1
/* end of On-chip Peripheral */
|
接下来使用 ENV 命令构建 scons --target=mdk5
打开 MDK 工程编译运行。
使用 list_device 命令就可以看到刚才加入的串口设备了。
1
2
3
4
5
6
7
| msh />list_device
device type ref count
-------- -------------------- ----------
uart4 Character Device 2
uart1 Character Device 1
pin Miscellaneous Device 0
msh />
|
访问串口设备
关于串口通信的基础知识这里就不累赘复述了,直接看 RT-Thread 中的 API.
应用程序通过 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() | 关闭设备 |
不管三七二十一,先上官网的示例代码:
1
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
| #include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"
#define THREAD_STACK_SIZE 1000 //线程栈大小(字节)
#define THREAD_TIMESLICE 40 //占用的滴答时钟数
#define SAMPLE_UART_NAME "uart1" /* 串口设备名称 */
static rt_thread_t serial_thread = RT_NULL;
static rt_uint8_t thread_priority = 20;
static rt_device_t serial; /* 串口设备句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
void serial_thread_entry(void *params)
{
serial = rt_device_find(SAMPLE_UART_NAME);
if(serial != RT_NULL)
{
/* step2:修改串口配置参数 */
config.baud_rate = BAUD_RATE_115200; //修改波特率为 115200
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);
}
}
//创建串口线程
int create_serial_thread(void)
{
serial_thread = rt_thread_create("serial_test", serial_thread_entry,
RT_NULL, THREAD_STACK_SIZE, thread_priority, THREAD_TIMESLICE);
if(serial_thread != RT_NULL)
{
rt_thread_startup(serial_thread);
}
return 0;
}
INIT_APP_EXPORT(create_serial_thread);
|
编译运行发现一切正常,如果有异常请下断点跟踪判断问题所在。走到这里说明我们的串口已经打开,接下来就是和具体串口协议相关的通信了。
向 PC 发数据
先看看硬件电路图,在外部排针上面完成接线,如下图使用的是 P1 那一排排针 TX 和 RX 使用的是 8 和 10 两个排针针脚, 14 脚接地线。
接线电路图
接线实物图
先发送一个测试字符串:
1
2
3
4
| char str[] = "hello RT-Thread!\r\n";
//发送字符串
rt_device_write(serial, 0, str, (sizeof(str) - 1));
|
在 PC 上用串口助手连接并接收数据如下:
中断接收及轮询发送
我们打开串口的模式为 RT_DEVICE_FLAG_INT_RX
表示中断接收数据模式,轮训发送数据模式,该参数还支持以下几种:
1
2
3
4
5
6
7
| #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 发送模式 */
|
可以通过如下函数来设置数据接收指示,当串口收到数据时,通知上层应用线程有数据到达 :
1
| 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 | 设置成功 |
可调用如下函数读取串口接收到的数据:
1
| 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 来判断错误状态 |
整个处理过程如下图:
实现代码:
1
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
| #include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"
#define THREAD_STACK_SIZE 1000 //线程栈大小(字节)
#define THREAD_TIMESLICE 40 //占用的滴答时钟数
#define SAMPLE_UART_NAME "uart1" /* 串口设备名称 */
static rt_thread_t serial_thread = RT_NULL;
static rt_uint8_t thread_priority = 20;
static rt_device_t serial; /* 串口设备句柄 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */
char str[] = "hello RT-Thread!\r\n";
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_read_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);
}
}
void serial_thread_entry(void *params)
{
serial = rt_device_find(SAMPLE_UART_NAME);
if(serial != RT_NULL)
{
//修改串口配置参数
config.baud_rate = BAUD_RATE_115200; //修改波特率为 115200
config.data_bits = DATA_BITS_8; //数据位 8
config.stop_bits = STOP_BITS_1; //停止位 1
config.bufsz = 128; //修改缓冲区 buff size 为 128
config.parity = PARITY_NONE; //无奇偶校验位
//控制串口设备。通过控制接口传入命令控制字,与控制参数 */
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
//打开串口设备。以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
//发送字符串
rt_device_write(serial, 0, str, (sizeof(str) - 1));
//初始化信号量
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
//设置接收回调函数
rt_device_set_rx_indicate(serial, uart_input);
//创建读线程
rt_thread_t thread = rt_thread_create("serial_read", serial_read_entry, RT_NULL, 1024, 25, 10);
//启动读线程
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
}
}
//创建串口线程
int create_serial_thread(void)
{
serial_thread = rt_thread_create("serial_init", serial_thread_entry,
RT_NULL, THREAD_STACK_SIZE, thread_priority, THREAD_TIMESLICE);
if(serial_thread != RT_NULL)
{
rt_thread_startup(serial_thread);
}
return 0;
}
INIT_APP_EXPORT(create_serial_thread);
|
关于上面的信号量实质上是为了解决线程间同步问题的,上面例子中只使用了一个线程,实际上我们可以创建多个线程来处理读取的数据。
信号量控制块结构的详细定义如下:
1
2
3
4
5
6
7
| struct rt_semaphore
{
struct rt_ipc_object parent; /* 继承自 ipc_object 类 */
rt_uint16_t value; /* 信号量的值 */
};
/* rt_sem_t 是指向 semaphore 结构体的指针类型 */
typedef struct rt_semaphore* rt_sem_t;
|
线程通过获取信号量来获得信号量资源实例,当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1(相当于停了一辆车):
1
| rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);
|
释放信号量可以唤醒挂起在该信号量上的线程(相当于车位管理员说有空余车位了):
1
| rt_err_t rt_sem_release(rt_sem_t sem);
|
当信号量的值等于零时,并且有线程等待这个信号量时,释放信号量将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量;否则将把信号量的值加 1。