完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
串口通讯协议简介
串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。 在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设; STM32 标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。 物理层 串口通讯的物理层有很多标准及变种,我们主要讲解 RS-232 标准 , RS-232 标准主要规定了信号的用途、通讯接口以及信号的电平标准。 使用 RS-232 标准的串口设备间常见的通讯结构见图 在上面的通讯方式中,两个通讯设备的“DB9 接口”之间通过串口信号线建立起连接,串口信号线中使用“RS-232 标准”传输数据信号。由于 RS-232 电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL 校准”的电平信号,才能实现通讯。 1、电平标准 根据通讯使用的电平标准不同,串口通讯可分为 TTL 标准及 RS-232 标准,见表 20-1。 我们知道常见的电子电路中常使用 TTL 的电平标准,理想状态下,使用 5V 表示二进制逻辑 1,使用 0V 表示逻辑 0;而为了增加串口通讯的远距离传输及抗干扰能力,它使用-15V 表示逻辑 1, +15V 表示逻辑 0。使用 RS232 与 TTL 电平校准表示同一个信号时的对比见图 20-2。 因为控制器一般使用 TTL 电平标准,所以常常会使用 MA3232 芯片对 TTL 及 RS-232电平的信号进行互相转换。 2、RS-232信号线 在最初的应用中, RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称“猫” )之间的通讯 ,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由)和数据通讯设备 DCE(调制调解器)。我们以这种通讯模型讲解它们的信号线连接方式及各个信号线的作用。 在旧式的台式计算机中一般会有 RS-232 标准的 COM 口(也称 DB9 接口),见图 20-3。 其中接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。在计算机中一般引出公头接口,而在调制调解器设备中引出的一般为母头,使用上图中的串口线即可把它与计算机连接起来。通讯时,串口线中传输的信号就是使用前面讲解的 RS-232 标准调制的。 在这种应用场合下, DB9 接口中的公头及母头的各个引脚的标准信号线接法见图 20-4及表 20-2。 表 20-2 DB9 信号线说明(公头,为方便理解,可把 DTE 理解为计算机, DCE 理解为调制调解器) 上表中的是计算机端的 DB9 公头标准接法,由于两个通讯设备之间的收发信号(RXD与 TXD)应交叉相连,所以调制调解器端的 DB9 母头的收发信号接法一般与公头的相反,两个设备之间连接时,只要使用“直通型”的串口线连接起来即可,见图 20-5。 串口线中的 RTS、 CTS、 DSR、 DTR 及 DCD 信号,使用逻辑 1 表示信号有效,逻辑 0表示信号无效。例如,当计算机端控制 DTR 信号线表示为逻辑 1 时,它是为了告知远端的调制调解器,本机已准备好接收数据, 0 则表示还没准备就绪。 在目前的其它工业控制使用的串口通讯中,一般只使用 RXD、 TXD 以及 GND 三条信号线,直接传输数据信号。而 RTS、 CTS、 DSR、 DTR 及 DCD 信号都被裁剪掉了,如果您在前面被这些信号弄得晕头转向,那就直接忽略它们吧。 协议层 串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在串口通讯的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据,其组成见图 20-6。 1、波特率 本章中主要讲解的是串口异步通讯,异步通讯中由于没有时钟信号(如前面讲解的 DB9接口中是没有时钟信号的),所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码, 图 20-6 中用虚线分开的每一格就是代表一个码元。常见的波特率为4800、 9600、 115200 等。 2、 通讯的起始和停止 串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑 0 的数据位表示,而数据包的停止信号可由 0.5、 1、 1.5 或 2 个逻辑 1 的数据位表示,只要双方约定一致即可。 3、有效数据 在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、 6、 7 或 8 位长。 4、数据校验 在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、 0 校验(space)、 1 校验(mark)以及无校验(noparity),它们介绍如下:
STM32 芯片具有多个 USART 外设用于串口通讯,它是 Universal Synchronous Asynchronous Receiver and Transmitter 的缩写,即通用同步异步收发器可以灵活地与外部设备进行全双工数据交换。有别于 USART,它还有具有 UART 外设(Universal Asynchronous Receiver and Transmitter),它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。 USART 满足外部设备对工业标准 NRZ 异步串行数据格式的要求,并且使用了小数波特率发生器,可以提供多种波特率,使得它的应用更加广泛。 USART 支持同步单向通信和半双工单线通信;还支持局域互连网络 LIN、智能卡(SmartCard)协议与 lrDA(红外线数据协会) SIR ENDEC 规范。 USART 支持使用 DMA,可实现高速数据通信,有关 DMA 具体应用将在 DMA 章节作具体讲解。 USART 在 STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一个 USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、指出运行出错位置等等。 STM32 的 USART 输出的是 TTL 电平信号,若需要 RS-232 标准的信号可使用MAX3232 芯片进行转换。 USART功能框图 1、功能引脚 TX:发送数据输出引脚。 RX:接收数据输入引脚。 SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。 nRTS:请求以发送(Request To Send), n 表示低电平有效。如果使能 RTS 流控制,当USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制。 nCTS:清除以发送(Clear To Send), n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。 SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。 USART 引脚在 STM32F429IGT6 芯片具体发布见表 20-3。 STM32F42xxx 系统控制器有四个 USART 和四个 UART,其中 USART1 和 USART6 的时钟来源于 APB2 总线时钟,其最大频率为 90MHz,其他六个的时钟来源于 APB1 总线时钟,其最大频率为 45MHz。 UART 只是异步传输功能,所以没有 SCLK、 nCTS 和 nRTS 功能引脚。 观察表 20-3 可发现很多 USART 的功能引脚有多个引脚可选,这非常方便硬件设计,只要在程序编程时软件绑定引脚即可。 2、数据寄存器 USART 数据寄存器(USART_DR)只有低 9 位有效,并且第 9 位数据是否有效要取决于USART 控制寄存器 1(USART_CR1)的 M 位设置,当 M 位为 0 时表示 8 位数据字长,当 M位为 1 表示 9 位数据字长,我们一般使用 8 位数据字长。 USART_DR 包含了已发送的数据或者接收到的数据。 USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写 TDR,一个专门用于接收的可读 RDR。当进行发送操作时,往 USART_DR 写入数据会自动存储在 TDR 内;当进行读取操作时,向 USART_DR读取数据会自动提取 RDR 数据。 TDR 和 RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把 TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到 RDR。 USART 支持 DMA 传输,可以实现高速数据传输,具体 DMA 使用将在 DMA 章节讲解 3、控制器 USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。使用 USART 之前需要向 USART_CR1 寄存器的 UE 位置 1 使能 USART。发送或者接收数据字长可选 8 位或 9 位,由 USART_CR1 的 M 位控制 发送器 当 USART_CR1 寄存器的发送使能位 TE 置 1 时,启动数据发送,发送移位寄存器的数据会在 TX 引脚输出,如果是同步模式 SCLK 也输出时钟信号。 一个字符帧发送需要三个部分:起始位+数据帧+停止位。起始位是一个位周期的低电平,位周期就是每一位占用的时间;数据帧就是我们要发送的 8 位或 9 位数据,数据是从最低位开始传输的;停止位是一定时间周期的高电平。 停止位时间长短是可以通过 USART 控制寄存器 2(USART_CR2)的 STOP[1:0]位控制,可选 0.5 个、 1 个、 1.5 个和 2 个停止位。默认使用 1 个停止位。 2 个停止位适用于正常USART 模式、单线模式和调制解调器模式。 0.5 个和 1.5 个停止位用于智能卡模式。 当选择 8 位字长,使用 1 个停止位时,具体发送字符时序图见图 20-8。 当发送使能位 TE 置 1 之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),接下来就可以往 USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待 USART 状态寄存器(USART_SR)的 TC 位为 1,表示数据传输完成,如果USART_CR1 寄存器的 TCIE 位置 1,将产生中断。 在发送数据时,编程的时候有几个比较重要的标志位我们来总结下。
如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。 接收完成后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置1,同时如果 USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断。 在接收数据时,编程的时候有几个比较重要的标志位我们来总结下。
接收器可配置为不同过采样技术,以实现从噪声中提取有效的数据。 USART_CR1 寄存器的 OVER8 位用来选择不同的采样采样方法,如果 OVER8 位设置为 1 采用 8 倍过采样,即用 8 个采样信号采样一位数据;如果 OVER8 位设置为 0 采用 16 倍过采样,即用 16 个采样信号采样一位数据。 USART 的起始位检测需要用到特定序列。如果在 RX 线识别到该特定序列就认为是检测到了起始位。起始位检测对使用 16 倍或 8 倍过采样的序列都是一样的。该特定序列为:1110X0X0X0000,其中 X 表示电平任意, 1 或 0 皆可。 8 倍过采样速度更快,最高速度可达 fPCLK/8, fPCLK为 USART 时钟,采样过程见图20-9。使用第 4、 5、 6 次脉冲的值决定该位的电平状态。 16 倍过采样速度虽然没有 8 倍过采样那么快,但得到的数据更加精准,其最大速度为fPCLK/16,采样过程见图 20-10。使用第 8、 9、 10 次脉冲的值决定该位的电平状态。 |
|
|
|
4、小数波特率生成
波特率指数据信号对载波的调制速率, 它用单位时间内载波调制状态改变次数来表示,单位为波特。比特率指单位时间内传输的比特数,单位 bit/s(bps)。对于 USART 波特率与比特率相等,以后不区分这两个概念。波特率越大,传输速率越快。 USART 的发送器和接收器使用相同的波特率。计算公式如下: 其中, fPLCK为 USART 时钟,参考表 20-3; OVER8 为 USART_CR1 寄存器的 OVER8位对应的值, USARTDIV 是一个存放在波特率寄存器(USART_BRR)的一个无符号定点数。其中DIV_Mantissa[11:0]位定义 USARTDIV 的整数部分, DIV_Fraction[3:0]位定义USARTDIV 的小数部分, DIV_Fraction[3]位只有在 OVER8 位为 0 时有效,否则必须清零。 例如,如果 OVER8=0, DIV_Mantissa=24 且 DIV_Fraction=10,此时 USART_BRR 值为 0x18A;那么 USARTDIV 的小数位 10/16=0.625;整数位 24,最终 USARTDIV 的值为24.625。 如果 OVER8=0 并且知道 USARTDIV 值为 27.68,那么 DIV_Fraction=16*0.68=10.88,最接近的正整数为 11,所以 DIV_Fraction[3:0]为 0xB; DIV_Mantissa=整数(27.68)=27,即位 0x1B。 如果 OVER8=1 情况类似,只是把计算用到的权值由 16 改为 8。 波特率的常用值有 2400、 9600、 19200、 115200。下面以实例讲解如何设定寄存器值得到波特率的值。 由表 20-3 可知 USART1 和 USART6 使用 APB2 总线时钟,最高可达 90MHz,其他USART 的最高频率为 45MHz。我们选取 USART1 作为实例讲解,即 fPLCK=90MHz。 当我们使用 16 倍过采样时即 OVER8=0,为得到 115200bps 的波特率,此时: 解得 USARTDIV=48.825125,可算得 DIV_Fraction=0xD, DIV_Mantissa=0x30,即应该设置 USART_BRR 的值为 0x30D。 在计算 DIV_Fraction 时经常出现小数情况,经过我们取舍得到整数,这样会导致最终输出的波特率较目标值略有偏差。下面我们从 USART_BRR 的值为 0x30D 开始计算得出实际输出的波特率大小。 由 USART_BRR 的值为 0x30D,可得 DIV_Fraction=13, DIV_Mantissa=48,所以USARTDIV=48+16*0.13=48.8125,所以实际波特率为: 115237;这个值跟我们的目标波特率误差为 0.03%,这么小的误差在正常通信的允许范围内。 8 倍过采样时计算情况原理是一样的。 5、检验控制 STM32F4xx 系列控制器 USART 支持奇偶校验。当使用校验位时,串口传输的长度将是 8 位的数据帧加上 1 位的校验位总共 9 位,此时 USART_CR1 寄存器的 M 位需要设置为1,即 9 数据位。将 USART_CR1 寄存器的 PCE 位置 1 就可以启动奇偶校验控制,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收数据时如果出现奇偶校验位验证失败,会见 USART_SR 寄存器的 PE 位置 1,并可以产生奇偶校验中断。 使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧+校验位+停止位。 6、中断控制 USART 有多个中断请求事件,具体见表 20-4。 USART初始化结构体详解 标准库函数对每个外设都建立了一个初始化结构体,比如 USART_InitTypeDef,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 USART_Init()调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。 初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在 stm32f4xx_usart.h 文件中,初始化库函数定义在 stm32f4xx_usart.c 文件中,编程时我们可以结合这两个文件内注释使用。 USART 初始化结构体 typedef struct { uint32_t USART_BaudRate; // 波特率 uint16_t USART_WordLength; // 字长 uint16_t USART_StopBits; // 停止位 uint16_t USART_Parity; // 校验位 uint16_t USART_Mode; // USART 模式 uint16_t USART_HardwareFlowControl; // 硬件流控制 } USART_InitTypeDef; 1) USART_BaudRate:波特率设置。一般设置为 2400、 9600、 19200、 115200。标准库 函 数 会 根 据 设 定 值 计 算 得 到 USARTDIV 值 , 见 公 式 20-1 , 并 设 置USART_BRR 寄存器值。 2) USART_WordLength:数据帧字长,可选 8 位或 9 位。它设定 USART_CR1 寄存器的 M 位的值。如果没有使能奇偶校验控制,一般使用 8 数据位;如果使能了奇偶校验则一般设置为 9 数据位。 3) USART_StopBits:停止位设置,可选 0.5 个、 1 个、 1.5 个和 2 个停止位,它设定USART_CR2 寄存器的 STOP[1:0]位的值,一般我们选择 1 个停止位。 4) USART_Parity : 奇 偶 校 验 控 制 选 择 , 可 选 USART_Parity_No( 无 校 验 ) 、USART_Parity_Even( 偶 校 验 ) 以 及 USART_Parity_Odd( 奇 校 验 ) , 它 设 定USART_CR1 寄存器的 PCE 位和 PS 位的值。 5) USART_Mode: USART 模式选择,有 USART_Mode_Rx 和 USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定 USART_CR1 寄存器的 RE 位和 TE 位。 6) USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效,可选有⑴使能 RTS、 ⑵使能 CTS、 ⑶同时使能 RTS 和 CTS、 ⑷不使能硬件流。 当使用同步模式时需要配置 SCLK 引脚输出脉冲的属性,标准库使用一个时钟初始化结构体 USART_ClockInitTypeDef 来设置,因此该结构体内容也只有在同步模式才需要设置。 USART 时钟初始化结构体 typedef struct { uint16_t USART_Clock; // 时钟使能控制 uint16_t USART_CPOL; // 时钟极性 uint16_t USART_CPHA; // 时钟相位 uint16_t USART_LastBit; // 最尾位时钟脉冲 } USART_ClockInitTypeDef; 1) USART_Clock:同步模式下 SCLK 引脚上时钟输出使能控制,可选禁止时钟输出(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模式发送,一般都需要开启时钟。它设定 USART_CR2 寄存器的 CLKEN 位的值。 2) USART_CPOL:同步模式下 SCLK 引脚上输出时钟极性设置,可设置在空闲时SCLK 引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定 USART_CR2 寄存器的 CPOL 位的值。 3) USART_CPHA:同步模式下 SCLK 引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定 USART_CR2 寄存器的 CPHA 位的值。 USART_CPHA 与 USART_CPOL 配合使用可以获得多种模式时钟关系。 4) USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK 引脚输 出 , 可 以 是 不 输 出 脉 冲 (USART_LastBit_Disable) 、 输 出 脉 冲(USART_LastBit_Enable)。它设定USART_CR2 寄存器的 LBCL 位的值。 USART1收发通信实验 USART 只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留USART 接口来实现与其他模块或者控制器进行数据传输,比如 GSM 模块, WIFI 模块、蓝牙模块等等。在硬件设计时,注意还需要一根“共地线”。 我们经常使用 USART 来实现控制器与电脑之间的数据传输。这使得我们调试程序非常方便,比如我们可以把一些变量的值、函数的返回值、寄存器标志位等等通过 USART发送到串口调试助手,这样我们可以非常清楚程序的运行状态,当我们正式发布程序时再把这些调试信息去除即可。 我们不仅仅可以将数据发送到串口调试助手,我们还可以在串口调试助手发送数据给控制器,控制器程序根据接收到的数据进行下一步工作。 首先,我们来编写一个程序实现开发板与电脑通信,在开发板上电时通过 USART 发送一串字符串给电脑,然后开发板进入中断接收等待状态,如果电脑有发送数据过来,开发板就会产生中断,我们在中断服务函数接收数据,并马上把数据返回发送给电脑。 USART1指令控制RGB彩灯实验 在学习 C 语言时我们经常使用 C 语言标准函数库输入输出函数,比如 printf、 scanf、getchar 等等。为让开发板也支持这些函数需要把 USART 发送和接收函数添加到这些函数的内部函数内。 正如之前所讲,可以在串口调试助手输入指令,让开发板根据这些指令执行一些任务,现在我们编写让程序接收 USART 数据,根据数据内容控制 RGB 彩灯的颜色。 重定向 prinft 和 scanf 函数 代码清单 20-9 重定向输入输出函数 ///重定向 c 库函数 printf 到串口,重定向后可使用 printf 函数 int fputc(int ch, FILE *f) { /* 发送一个字节数据到串口 */ USART_SendData(USARTx, (uint8_t) ch); /* 等待发送完毕 */ while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET); return (ch); } ///重定向 c 库函数 scanf 到串口,重写向后可使用 scanf、 getchar 等函数 int fgetc(FILE *f) { /* 等待串口输入数据 */ while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET); return (int)USART_ReceiveData(USARTx); } 在 C 语言标准库中, fputc 函数是 printf 函数内部的一个函数,功能是将字符 ch 写入到文件指针 f 所指向文件的当前写指针位置,简单理解就是把字符写入到特定文件中。我们使用 USART 函数重新修改 fputc 函数内容,达到类似“写入”的功能。 还有一点需要注意的,使用 fput 和 fgetc 函数达到重定向 C 语言标准库输入输出函数必须在 MDK 的工程选项把“Use MicroLIB”勾选上, MicoroLIB 是缺省 C 库的备选库,它对标准 C 库进行了高度优化使代码更少,占用更少资源。 为使用 printf、 scanf 函数需要在文件中包含 stdio.h 头文件。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1632 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1559 浏览 1 评论
985 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
688 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1605 浏览 2 评论
1869浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
652浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
521浏览 3评论
539浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
508浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 06:13 , Processed in 0.837423 second(s), Total 81, Slave 64 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号