RT-Thread零基础快速入门第9讲——串口(UART/RS485)

RT-Thread零基础快速入门第9讲——串口(UART/RS485)

前言

串口是单片机最常用的通讯方式之一,关于串口的介绍在RT-thread官网上已经有非常详细的介绍了,我这里就不多讲了,今天主要讲一讲官网上没有的东西,让你更加深入的了解RT-thread是如何配置和使用串口进行数据收发的。
RT-thread官网上关于UART的资料:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart_v1/uart

一、配置底层引脚

1、确定串口引脚

先从原理图查看自己需要使用的串口号及对应的引脚。
比如我要使用串口3,引脚是PB10和PB11(USART3除了这一组引脚还有PC10、PC11)
在这里插入图片描述

2、配置底层引脚

1)配置串口
打开keil工程,使用STM32CubeMX配置底层接口(路径一般在工程目录下\board\CubeMX_Config文件夹里面)
不懂的同学可以看下我之前的博客。
RT-Thread零基础快速入门第1讲——新建工程

先打开对应的串口(Mode选择Asynchronous即可),然后检查GPIO是否和原理图一致。
在这里插入图片描述
如果不一致的话可以在右边的芯片图上面找到正确的引脚,然点击引脚,修改该引脚为串口功能,最后再回到上一步,检查一下串口是否已经修改成正确的引脚了。
在这里插入图片描述
在这里插入图片描述
提示:使用STM32CubeMX配置串口只需要打开串口和配置引脚,其他的像波特率、NVIC这些都需要管,因为这些都是在RTT应用层初始化的时候配置的,即使在这里改了也是不起作用的。

2)生成新的工程
点击右上角的GENERATE CODE,生成新工程即可。
注:如果生成工程时提示你是否需要下载新版本的固件库,可以下载也可以继续使用旧的,一般都是没问题的。
在这里插入图片描述

二、编写应用层代码

1、打开串口使能

用env打开工程,进入menuconfig配置页面,打开串口使能。
提示:这里的使能和上面STM32CubeMX打开串口是不一样的,env配置使能之后实际上是打开一个宏定义,打开之后才能调用RTT串口相关的库函数,而STM32CubeMX使能则是把HAL库串口相关的函数加进来。

如果你不知道env怎么使用,可以在下面这个链接查看。
env使用方法:https://www.rt-thread.org/document/site/programming-manual/env/env/#bsp-menuconfig
在这里插入图片描述
配置好之后,退出保存即可。
注意:如果menuconfig里面没有你要使用的串口号怎么办,这个问题请跳到后面查看 “常见问题解答”。

2、重新生成工程

在env输入下面的命令,重新生成新的工程。
提示:会使用env的话应该都知道这个操作。

scons --target=mdk5

3、编写串口收发代码

串口收发的示例代码可以在RT-thread官网查看,有中断接收的示例,也有DMA接收的示例,都是写的很详细的了,拷贝过来改一下就可以用了。

你也可以在menuconfig里面打开串口接收的示例。
在这里插入图片描述
打开示例之后要重新生成工程,也就是上面第2步的操作。

建议:如果只是测试串口功能的话可以直接添加示例文件,但是实际应用中还是建议新建一个文件,然后编写串口的应用代码,再把这个文件加入到keil工程里面。

参考:RT-Thread零基础快速入门第2讲——添加新文件到工程

不管用哪种方法,示例代码添加进来之后还是要按照自己的需求修改配置,比如串口号,波特率,串口接收的数据处理等。
我这里简单的举个例子,具体怎么改还是要看你的功能需求。

以中断接收的demo为例:
1)修改串口号
直接修改SAMPLE_UART_NAME这个宏即可,比如我这里用的是串口3,就改成"uart3"

#define SAMPLE_UART_NAME       "uart3"      /* 串口设备名称 */

2)修改波特率
在查找串口(rt_device_find)之后,打开串口(rt_device_open)之前添加下面这段代码即可。

/* 修改串口配置参数 */
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;  // 初始化配置参数
config.baud_rate = BAUD_RATE_9600;        //修改波特率为 9600
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);

如下图所示:
在这里插入图片描述
3)串口数据接收处理
如果数据不多处理不复杂,可以直接在接收数据的线程进行数据处理,如下图所示:
在这里插入图片描述
如果数据比较多或者处理比较复杂需要消耗的时间比较长的情况下,就不太建议在接收的线程进行数据处理了,因为在这里处理数据会影响后续的数据接收。
推荐几种常用的解决方法:
1:使用消息队列把接收到的数据传输到其他线程,在其他的线程做统一的数据处理。
2:如果接收是以中断的方式,可以考虑在接收的时候就做先做简单的处理,比如用一个switch语句,每接收一个数据就处理一次,这样就可以把每个字节接收的间隙时间利用起来。这个方法也可以用来判断数据帧是否完整接收完成,是否有错误的数据帧。
数据处理的方法有很多,各有优劣,具体用哪种方式则要看实际的情况,适合的方法才是最好的。

利用switch语句处理接收数据的示例代码:

switch(count)
{
    
    
    case 0:{
    
    
        if(ch == 0xFC){
    
                      // 帧头
            uart_rx_buffer[i++] = ch;
            count ++;
        }
        else{
    
    
            i = 0;
            count = 0;
        }
        break;
    }
    case 1:{
    
                                 // 地址
        if(ch == 1 || ch == 2){
    
    
            uart_rx_buffer[i++] = ch;
            count ++;
        }
        else{
    
    
            i = 0;
            count = 0;
        }
        break; 
    } 
    case 2:{
    
                                 // 命令
            if(ch == 0xD4){
    
    
            uart_rx_buffer[i++] = ch;
            count ++;
        }
        else{
    
    
            i = 0;
            count = 0;
        }
        break;
    }
    case 3:{
    
    
            uart_rx_buffer[i++] = ch;     // 数据高位
            count ++;
        break;
    }
    case 4:{
    
    
            uart_rx_buffer[i++] = ch;     // 数据高位
            count ++;
        break;
    }
    case 5:{
    
    
            uart_rx_buffer[i++] = ch;     // 校验
            i = 0;
            count = 0; 
            if(verify_check(uart_rx_buffer, 5) == uart_rx_buffer[5]) // 检查校验
            {
    
    
                rt_sem_release(rs485_timeout_sem);
                uart_485_data_handle(uart_rx_buffer);
            }
        break; 
    }
    default: 
        i = 0;
        count = 0;
        break;
}

三、进阶学习

1、串口的硬件小知识

串口常用的电平有三种:TTL、RS232、RS485。

类型 说明
TTL 电压范围0V至+5V,高电平>2.4V,低电平<0.4V,一般MCU直接输出的串口就是TTL电平的
RS232 电压范围-15V至+15V,高电平为-3V至-15V,低电平为+3V至+15V
RS485 电压范围-7V至+12V,不同于TTL和RS232,RS485采用的差分信号负逻辑,高电平为两线间的电压差-2V至-6V,低电平为两线间的电压差+2V至+6V
USB 有单独的协议,与TTL、RS232和RS485都不同

三种电平之间不能直接通信,需要通过转换芯片转换成相同的电平。除了这三种电平以外,我们常常还用到USB,USB有自己的协议(2.0、3.0、3.1等),USB和TTL、RS232和RS485这些电平之间也是不能直接通信的,也需要转换。一般单片机或者其他MCU的串口电平都是TTL的,而PC端一般使用USB。

举个例子:
如果MCU的串口需要连接到PC端,那么就需要实现TTL和USB直接的转换,常用的方法如下:
1、通过一个USB转TTL的芯片转换,可以放在主板上,也可以用那种转换小板,这个大家应该比较熟悉了,某宝上面随处可见。
2、通过一个TTL转232的芯片转换,然后再接一根USB转232的连接线,这个线常用DB9接口。
3、通过一个TTL转485的芯片转换,然后再接一根USB转485的连接线。

TTL一般是板内模块间通讯用的比较多,因为串口线过长会有线损,影响通讯,因此,外接的传感器和MCU的通讯大多使用RS232和RS485这两种,像RS485,串口线即使长达一百米,也不影响正常通讯。

2、RS485的使用

上面简单介绍了三种电平硬件上的区别,那么在编程上又有什么区别呢?
对于TTL和RS232来说,编程都是一样的,只要配置好UART的收发即可,电平的转换都是通过转换芯片来完成的,与代码无关。RS485也是类似的,电平的转换也是通过转换芯片来完成,但不同的是RS232是可以同时收发的,而RS485在同一时间只能发送或者接收,因此,RS485的转换芯片多了一个使能引脚,这个使能脚是用来切换发送模式和接收模式的,一般是由MCU来控制。
在这里插入图片描述

我们在写代码的时候只要把RS485这个使能脚的控制加进去就可以了。

1)先定义485的使能引脚,以及加入GPIO配置要用到的头文件。

#include <rtdevice.h>
#include <board.h>
#define RS485_RE  GET_PIN(G, 8)

提示:引脚号用宏定义,这样的话如果以后要修改引脚,就不需要把每个函数都改一遍。

2)在串口初始化的时候把485使能引脚的初始化也加进去,默认设置为接收模式。

rt_pin_mode(RS485_RE, PIN_MODE_OUTPUT);   // 配置RS485使能引脚为输出
rt_pin_write(RS485_RE, PIN_LOW);          // 配置RS485为接收模式(一般低电平是接收模式,高电平是发送模式,当然,也有相反的,主要还是看485用什么芯片)

3)在串口发送数据前切换485为发送模式,发送完成后再切回接收模式。

rt_pin_write(RS485_RE, PIN_HIGH);         // 发送模式 
rt_device_write(serial, 0, &ch, 1);       // 发送数据
rt_pin_write(RS485_RE, PIN_LOW);          // 接收模式

四、常见问题解答

1、ENV配置里面没有我要用的串口号

问题分析:这是因为你这个工程menuconfig页面的配置文件并没有把所有串口和配置都添加进来。
解决办法:在工程目录下board文件夹里面有一个Kconfig文件,这个就是menuconfig页面的配置文件,在里面找到串口的配置,可以把原有的配置修改成你想要的串口号,也可以照猫画虎的抄一遍原有的配置,然后再修改成你想要的串口号。修改完之后重新打开menuconfig即可,如果报错打不开了,说明的改的有问题,有语法错误。
在这里插入图片描述
还有一个办法就是直接在工程文件里面的rtconfig.h添加串口使能的宏定义,实际上使用env配置修改的也是这个文件的宏,但是这样做有一个问题就是每次你使用env配置好参数之后,你都要再手动添加一遍串口使能的宏,因为每次使用env配置参数的时候都会按照Kconfig文件覆盖一遍,而Kconfig又没有你手动添加的这个宏,所以,最好还是用上面的方法,在根本上解决问题。
在这里插入图片描述

2、ENV配置的串口配置没有DMA模式

问题分析:这个问题和上面那个问题其实是一样的,修改Kconfig即可

3、ENV配置没错,硬件电路也是对的,但是串口就是收不到数据

问题分析:假如你ENV的配置是没错的,硬件电路也确认过是正常的,使用list_device也能查找到uart设备,但是就是收不到数据,有可能是底层串口没有配置好,比如底层的引脚号配置的不对或者串口根本就没有打开,这种情况在修改了电路图或者工程的时候很容易出现,也很容易忽视,因为这个问题编译的时候并不会报错,你不仔细核对原理图是发现不了的。
解决办法:使用STM32CubeMX配置好串口的参数,具体的操作上面已经介绍过,就不多说了。

4、STM32CubeMX、ENV及串口初始化等软件部分都确认是对的,但就是没有数据

问题分析:这种情况就要结合硬件一起排查了,最快的排查方法就是直接上示波器或者逻辑分析仪,可以单独写一个测试线程,让单片机定时发数据(发什么数据不重要),然后用示波器查看MCU串口的TX引脚的波形,正常的波形是很规律的,每一帧的数据间隔也会跟你定时的时间一致,并且数据帧的波形都是比较漂亮的方波。如果没有波形,排除MCU损坏的情况的话,肯定还是软件没有配置好。接收的测试也是一样的,可以通过PC端的串口助手定时发送数据,然后测试MCU串口的RX引脚,看看波形是否正常,如果波形正常,但是单片机就是没有收到数据,那肯定还是软件没有配置好。

5、串口每次都只能通过输入命令启动吗

命令行输入只是为了方便我们测试,实际使用的话可以使用INIT_APP_EXPORT自动加载函数,或者在其他函数、线程里面调用。
例如:

INIT_APP_EXPORT(uart_sample);

五、结束语

串口的使用其实非常简单,但是实际使用的时候往往会因为一些小问题折腾了半天,这个时候一定要冷静的分析问题,先检查一遍硬件电路,再按流程检查一遍配置和代码,最后实在没办法了就上示波器吧。
好了,关于RT-thread串口的介绍就到这里,如果还有什么问题,欢迎在评论区留言。如果这篇文章能够帮到你,就给我点个赞吧,如果想了解更多RT-thread和单片机的内容,可以关注一下博主,后续我还会继续分享更多的经验给大家。

RT-thread相关教程汇总:https://blog.csdn.net/ShenZhen_zixian/article/details/120563891

猜你喜欢

转载自blog.csdn.net/ShenZhen_zixian/article/details/120547284