24、STM32 UART中断与新项目创建全解析

STM32 UART中断与新项目创建全解析

1. UART中断服务例程及相关函数

在STM32的开发中,UART(通用异步收发传输器)的中断处理是非常重要的一部分。下面我们来详细了解UART中断服务例程以及相关的函数。

1.1 UART中断服务例程(ISR)

UART中断服务例程用于处理接收到的数据。以下是 usart1_isr 函数的代码:

void usart1_isr(void) {
    char ch;
    BaseType_t hptask = pdFALSE;

    while ( ((USART_SR(USART1) & USART_SR_RXNE) != 0) ) {
        // Have received data:
        ch = usart_recv(USART1);

        // Use best effort to send byte to queue
        xQueueSendFromISR(uart_rxq, &ch, &hptask);
    }
}

这个函数的主要功能是在接收到数据时,将数据从UART接收寄存器中读取出来,并将其发送到队列 uart_rxq 中。

1.2 uart_getc 函数

uart_getc 函数用于从队列中获取接收到的数据。代码如下:

static char uart_getc(void) {
    char ch;

    while ( xQueueReceive(uart_rxq, &ch, 0) != pdPASS )
        taskYIELD();
    gpio_toggle(GPIOC, GPIO13);
    return ch;
}

当队列中有数据时,函数会将数据取出并返回;如果队列中没有数据,函数会让出CPU控制权,等待数据的到来。

1.3 main_task 函数

main_task 函数是一个任务,其主要功能是接收字符并将其发送回UART。代码如下:

#define INVERT_CASE 0
static void main_task(void *args __attribute((unused))) {
    for (;;) {
        // Block until char received
        char ch = uart_getc();
#if INVERT_CASE
        if ( islower(ch) )
            ch = toupper(ch);
        else if ( isupper(ch) )
            ch = tolower(ch);
#endif
        uart_putc(ch);
    }
}

通过修改 INVERT_CASE 的值为1,可以将接收到的字符进行大小写转换。

1.4 uart_putc 函数

uart_putc 函数用于将字符发送到UART。代码如下:

static inline void uart_putc(char ch) {
    // Queue the byte to send
    while ( xQueueSend(uart_txq, &ch, 0) != pdPASS )
        taskYIELD();
    if ( ch == '\n' ) {
        // When we see LF, also send CR
        ch = '\r';
        while ( xQueueSend(uart_txq, &ch, 0) != pdPASS )
            taskYIELD();
    }
}

该函数会将字符发送到队列 uart_txq 中,如果队列已满,函数会让出CPU控制权。

1.5 tx_task 函数

tx_task 函数用于从队列中取出字符并发送到UART。代码如下:

static void tx_task(void *args __attribute((unused))) {
    char ch;

    for (;;) {
        while ( xQueueReceive(uart_txq, &ch, portMAX_DELAY) != pdPASS )
            taskYIELD();

        // Received a char to transmit:
        while ( !usart_get_flag(USART1, USART_SR_TXE) )
            taskYIELD(); // Not ready to send
        usart_send(USART1, ch);
    }
}

当队列中有数据时,函数会等待UART准备好接收数据,然后将数据发送出去。

2. 运行演示

在运行演示之前,需要进行一些配置。

2.1 配置演示
  • 修改 INVERT_CASE 的值来决定是否对数据进行大小写转换。
#define INVERT_CASE 0
  • 重新构建项目:
$ make clobber
$ make
  • 烧录程序:
$ make flash
2.2 配置终端

连接串口设备到STM32并插入USB端口后,启动 minicom (或其他终端模拟器):

$ minicom -D /dev/cu.usbserial-A50285BI -s

确保在 minicom 中关闭流控制,并将波特率和其他串口参数配置为115200波特,模式8N1。

2.3 演示操作

进入 minicom 的文件传输协议配置选项,添加一个名为“cat”的新协议。然后按下 Meta - S 调用“Upload”菜单,选择“cat”,可以选择一个文本文件进行传输。

3. 数据丢失问题

由于演示中没有启用流控制,在高波特率下可能会出现数据丢失的问题。可以通过降低波特率来尝试消除数据丢失。同时,STM32 USART设备和 libopencm3 库支持硬件流控制,但将其集成到程序中超出了本文的范围。

4. 新项目创建

创建一个新的STM32项目可以按照以下步骤进行:

4.1 项目创建

首先,定位到正确的起始目录:

$ cd ~/stm32f103c8t6/rtos

然后,使用 make 命令创建项目:

$ make -f Project.mk PROJECT=myproj

创建完成后,项目子目录会被填充一些文件,包括 FreeRTOSConfig.h Makefile main.c 等。

4.2 Makefile相关宏

Makefile 中使用了一些宏来定义项目,下面是一些重要的宏:
- BINARY :定义编译后的可执行文件的名称,默认是 main
- SRCFILES :定义要编译到最终可执行文件中的源文件名称,默认包括 main.c rtos/heap_4.c 等。
- LDSCRIPT :指向项目目录中的链接脚本文件,通常为 stm32f103c8t6.ld
- DEPS :用于定义特殊的依赖项。
- CLOBBER :用于定义在 make clobber 时要删除的文件。

以下是默认的 Makefile 代码:

#########################################################
#  Project Makefile
#########################################################

BINARY     = main
SRCFILES    =  main.c rtos/heap_4.c rtos/list.c rtos/port.c \
                  rtos/queue.c rtos/tasks.c rtos/opencm3.c
LDSCRIPT   = stm32f103c8t6.ld

# DEPS      =  # Any additional dependencies for your build
# CLOBBER  += # Any additional files to be removed with \
                         "make clobber"

include ../../Makefile.incl
include ../Makefile.rtos

#########################################################
#  NOTES:
#    1. remove any modules you don't need from SRCFILES
#    2.  "make clean" will remove *.o etc., but leaves *.elf, *.bin
#    3.  "make clobber" will "clean" and remove *.elf, *.bin etc.
#    4. "make flash" will perform:
#       st-flash write main.bin 0x8000000
#########################################################
4.3 其他配置
  • 包含的Makefile :项目中包含了 ../../Makefile.incl ../Makefile.rtos ,分别用于定义项目构建的宏和规则,以及添加 ./rtos 子目录到FreeRTOS构建的包含文件搜索路径。
  • 头文件依赖 :可以通过添加 Makefile 依赖规则来处理头文件的变化,例如:
main.o: myproj.h
  • 编译选项 :可以为特定模块添加特殊的编译选项,例如:
ugui.o:  CFLAGS += -Wno-parentheses
4.4 烧录操作

项目构建完成后,使用以下命令进行烧录:

$ make flash

对于支持128K闪存的STM32F103C8T6芯片,可以使用以下命令进行烧录:

$ make bigflash
5. FreeRTOS组件

在项目中,FreeRTOS组件是重要的一部分。其中, rtos/opencm3.c 模块用于将 libopencm3 框架连接到FreeRTOS。以下是该模块的代码:

/* Warren W. Gay VE3WWG
 *
 *  To use libopencm3 with FreeRTOS on Cortex - M3 platform, we must
 * define three interlude routines.
 */
#include "FreeRTOS.h"
#include "task.h"
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/cm3/nvic.h>

extern void vPortSVCHandler( void ) __attribute__ (( naked ));
extern void xPortPendSVHandler( void ) __attribute__ (( naked ));
extern void xPortSysTickHandler( void );

void sv_call_handler(void) {
    vPortSVCHandler();
}

void pend_sv_handler(void) {
    xPortPendSVHandler();
}

void sys_tick_handler(void) {
    xPortSysTickHandler();
}

/* end opncm3.c */
6. 总结

通过以上内容,我们了解了STM32 UART中断处理的相关函数和流程,以及如何创建一个新的STM32项目。在UART中断处理中,使用队列可以有效地管理接收到的数据;在项目创建过程中,合理配置 Makefile 和相关宏可以提高开发效率。同时,我们也提到了数据丢失和FreeRTOS组件等相关问题,希望对大家的开发有所帮助。

表格:Minicom “cat”文件传输协议设置

参数
Name Y
U/D U
FullScr Y
IO - Red. Y
Multi N

流程图:新项目创建流程

graph TD;
    A[定位起始目录] --> B[创建项目子目录];
    B --> C[生成Makefile等文件];
    C --> D[编辑FreeRTOSConfig.h];
    D --> E[编辑Makefile SRCFILES];
    E --> F[编辑stm32f103c8t6.ld(可选)];
    F --> G[make];
    G --> H[make flash];
7. 关键技术点分析
7.1 UART中断处理优势

UART中断处理通过引入接收中断,显著改善了UART接收器的性能。传统的UART外设数据寄存器容量有限,而利用FreeRTOS内核的队列结合RAM内存,有效突破了这一限制。当有数据到达时,中断服务例程(ISR)会迅速响应,将数据从UART接收寄存器读取出来,并存储到队列中。这样一来,即使数据连续快速到达,也不会因为寄存器溢出而导致数据丢失。例如,在高速数据传输场景下,中断处理能够及时捕获每一个数据,保证数据的完整性。

7.2 FreeRTOS队列的使用

FreeRTOS队列在整个系统中起到了数据缓冲和任务间通信的重要作用。在UART中断处理中,队列 uart_rxq 用于存储接收到的数据, uart_txq 用于存储待发送的数据。使用队列而非传统的内存数组缓冲区,具有以下优点:
- 线程安全 :FreeRTOS队列提供了线程安全的操作接口,多个任务可以安全地对队列进行读写操作,避免了数据竞争和不一致的问题。
- 阻塞机制 :队列的读写操作可以设置阻塞时间,当队列为空或已满时,任务可以选择阻塞等待,直到队列状态满足条件。这种机制使得任务的执行更加高效,避免了不必要的轮询。
- 灵活性 :队列可以存储不同类型的数据,并且可以根据需要动态调整队列的长度。

7.3 ISR不阻塞执行的原因

中断服务例程(ISR)必须保证不阻塞执行,这是因为ISR是在中断上下文中执行的,具有较高的优先级。如果ISR阻塞,会导致其他中断无法及时响应,从而影响系统的实时性和稳定性。例如,在 usart1_isr 函数中,使用 xQueueSendFromISR 函数将数据发送到队列时,该函数是专门为ISR设计的,能够在不阻塞的情况下完成数据的发送。如果使用普通的队列发送函数,可能会因为队列已满而导致ISR阻塞。

7.4 usart_send usart_send_blocking 的选择

tx_task 函数中,使用 usart_send 函数而不是 usart_send_blocking 函数来发送数据。这是因为 usart_send_blocking 函数会一直等待UART外设准备好接收新数据,期间不会释放CPU资源,而 usart_send 函数在UART未准备好时会让出CPU控制权,使得其他任务可以继续执行。由于 libopencm3 库不知道我们正在使用FreeRTOS内核, usart_send_blocking 函数没有实现任务调度的功能,因此在多任务环境下,使用 usart_send 函数能够更好地实现任务的并发执行。

8. 常见问题及解决方法
8.1 数据丢失问题

在高波特率下,由于没有启用流控制,可能会出现数据丢失的问题。解决方法如下:
- 降低波特率 :降低数据传输的波特率,减少数据发送的速度,使得接收器有足够的时间处理每一个数据。例如,将波特率从115200降低到9600。
- 启用硬件流控制 :STM32 USART设备和 libopencm3 库支持硬件流控制,通过配置相应的引脚和寄存器,可以实现数据传输的流量控制。当接收器处理不过来时,发送器会暂停发送数据,避免数据丢失。

8.2 编译和烧录问题
  • 编译错误 :如果在编译过程中出现错误,首先检查代码的语法错误,特别是头文件的包含、函数调用和变量定义等方面。另外,确保 Makefile 中的 SRCFILES 宏包含了所有需要编译的源文件。
  • 烧录失败 :烧录失败可能是由于硬件连接问题、烧录工具版本不兼容等原因导致的。检查STM32与烧录器的连接是否正常,确保烧录工具(如 st-flash )的版本支持目标芯片的闪存容量。对于支持128K闪存的STM32F103C8T6芯片,使用 make bigflash 命令进行烧录。
9. 进一步学习建议
9.1 深入研究FreeRTOS

FreeRTOS是一个功能强大的实时操作系统内核,除了队列之外,还提供了任务管理、信号量、互斥锁等丰富的功能。可以深入学习FreeRTOS的官方文档和示例代码,了解其更多的特性和应用场景。例如,学习如何使用信号量来实现任务间的同步,如何使用互斥锁来保护共享资源。

9.2 探索硬件流控制

虽然本文没有详细介绍硬件流控制的实现,但它是解决数据丢失问题的有效方法。可以查阅STM32 USART设备和 libopencm3 库的相关文档,学习如何配置和使用硬件流控制。通过实际项目实践,掌握硬件流控制的原理和应用。

9.3 优化项目代码

在现有项目的基础上,可以对代码进行优化,提高系统的性能和稳定性。例如,优化中断服务例程的执行时间,减少中断处理的延迟;对队列的长度和任务的优先级进行合理调整,提高系统的并发处理能力。

表格:UART相关函数总结

函数名 功能 关键代码
usart1_isr UART中断服务例程,将接收到的数据存入队列 while ( ((USART_SR(USART1) & USART_SR_RXNE) != 0) ) { ch = usart_recv(USART1); xQueueSendFromISR(uart_rxq, &ch, &hptask); }
uart_getc 从队列中获取接收到的数据 while ( xQueueReceive(uart_rxq, &ch, 0) != pdPASS ) taskYIELD();
main_task 接收字符并发送回UART,可进行大小写转换 char ch = uart_getc(); #if INVERT_CASE if ( islower(ch) ) ch = toupper(ch); else if ( isupper(ch) ) ch = tolower(ch); #endif uart_putc(ch);
uart_putc 将字符发送到UART队列 while ( xQueueSend(uart_txq, &ch, 0) != pdPASS ) taskYIELD();
tx_task 从队列中取出字符并发送到UART while ( xQueueReceive(uart_txq, &ch, portMAX_DELAY) != pdPASS ) taskYIELD(); while ( !usart_get_flag(USART1, USART_SR_TXE) ) taskYIELD(); usart_send(USART1, ch);

流程图:UART数据处理流程

graph TD;
    A[数据到达UART] --> B[触发中断];
    B --> C[执行usart1_isr];
    C --> D[将数据存入uart_rxq];
    D --> E[main_task从uart_rxq获取数据];
    E --> F[处理数据(可选大小写转换)];
    F --> G[uart_putc将数据存入uart_txq];
    G --> H[tx_task从uart_txq取出数据];
    H --> I[发送数据到UART];

通过以上对STM32 UART中断处理和新项目创建的详细介绍,希望能够帮助读者更好地理解和掌握相关技术,在实际项目开发中灵活运用。同时,鼓励读者不断探索和学习,进一步提升自己的开发能力。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值