零基础国产GD32单片机编程入门(二十五)USB口介绍及CDC类虚拟串口通讯详解及源码

news2024/12/23 14:18:49

文章目录

    • 一.概要
    • 二.USB2.0基本介绍及虚拟串口介绍
    • 三.GD32单片机USB模块框图
    • 四.GD32单片机USB设备模式
    • 五.GD32F103C8T6 USB设备CDC类
    • 六.配置一个USB虚拟串口收发例程
    • 七.工程源代码下载
    • 八.小结

一.概要

GD32F103C8T6 USB虚拟串口是一种采用GD32F103C8T6单片机,通过USB接口连接电脑,将电脑的USB接口转换成串口接口,实现与电脑的通信的一种转换器。它可以实现与电脑的通信,还可以实现与外部设备的通信,广泛应用于工业控制、智能家居、智能硬件等领域。

本文介绍了GD32单片机USB口的基本概念,内部结构,以及用USB虚拟串口进行数据通讯的例程。

二.USB2.0基本介绍及虚拟串口介绍

USB2.0使用一对差分信号传输数据,并可以为USB设备提供电源。差分信号名称一般标示为“D+”和“D-”。
USB2.0可以支持三种传输速率:低速USB设备传输速率为1.5Mbps,全速USB设备传输速率为12Mbps,高速USB设备传输速率为480Mbps。GD32F103C8T6就属于全速USB设备传输,最快12Mbps。
在硬件电路方面,全速USB设备内部的“D+”信号应该通过1.5K的电阻上拉到3~3.6V。

USB虚拟串口,简称VCP,是Virtual COM Port的简写,它是利用 USB的 CDC类来实现的一种通信接口。我们可以利用GD32自带的USB功能,来实现一个USB虚拟串口,从而通过USB,实现电脑与GD32单片机的数据互传。

三.GD32单片机USB模块框图

GD32单片机通用串行总线全速设备接口(USBD)模块提供了一个实现符合USB 2.0全速协议外设的方案。它内部包含了一个USB物理层而不需要额外的外部物理层芯片。USBD支持USB 2.0协议所定义的四种传输类型(控制、批量、中断和同步传输)

在这里插入图片描述

SIE
• 硬件识别同步信号、进行比特填充、产生以及校验CRC、产生以及验证PID、握手 。
• 根据外设事件来产生SOF、复位信号等。

Packet Buffer interface
• 通过一组收、发buffer来管理512字节的local memory。
• 硬件根据来自SIE的请求来选择合适的buffer。

寄存器
• EndPoint相关的寄存器:该EP的传输类型、地址、当前状态 。
• 控制寄存器:控制USB模块事件(比如唤醒和休眠)和反应USB模块当前状态 。
• 中断寄存器:中断掩码,记录事件。

根据USB标准定义,USB全速模块采用了固定的48MHz时钟。要使用USBD,需要打开两个时钟,一个是USB控制器时钟,它的频率必须配到48MHz,另一个是APB1到USB接口时钟,它也是APB1的总线时钟,其频率可以高于也可以低于48MHz。

四.GD32单片机USB设备模式

USB一般有两种模式,主机模式,设备模式。

USB主机模式:‌在主机模式下,‌单片机能够枚举外部USB设备,‌如键盘、‌鼠标、‌闪存盘等,‌并对其进行配置和管理。‌这种模式适用于需要同时连接多个外部设备并进行数据交换的复杂应用场景。

USB设备模式:‌在设备模式下,‌单片机作为USB设备的角色,‌可以与主机进行通信。‌这包括配置USB设备描述符、‌初始化USB控制器、‌编写类处理函数等,‌以实现特定的通信需求。‌GD32支持多种USB类,‌如CDC(‌通信设备类)‌、‌HID(‌人机接口设备类)‌、‌MSC(‌大容量存储类)‌等,‌以满足不同的应用场景。

GD32F103C8T6只支持设备模式,在设备模式下,GD32可以模拟各种USB类设备,如键盘、鼠标、存储设备等,开发者需要配置USB接口并实现特定的USB类。

在这里插入图片描述

五.GD32F103C8T6 USB设备CDC类

CDC(Communication Device Class)类是 USB2.0 标准下的一个子类,定义了通信相关设备的抽象集合,我们虚拟串口通信就是CDC类。USB2.0标准下定义了很多子类,有音频类,CDC类,HID,打印,大容量存储类HUB,智能卡等等,这些在urb.org 官网上有具体的定义,这里我们主要讲的是通信类CDC。

USB CDC类的通信部分主要包含三部分:枚举过程、虚拟串口操作和数据通信。其中虚拟串口操作部分并不一定强制需要,因为若跳过这些虚拟串口的操作,实际上USB依然是可以通信的,之所以会有虚拟串口操作,主要是我们通常使用PC作为Host端,在PC端使用一个串口工具来与其进行通信,PC端的对应驱动将其虚拟成一个普通串口,这样一来,可以方便PC端软件通过操作串口的方式来与其进行通信,但实际上,Host端与Device端物理上是通过USB总线来进行通信的,与串口没有关系,这一虚拟化过程,起决定性作用的是对应驱动,包含如何将每一条具体的虚拟串口操作对应到实际上的USB操作。这里需要注意地是,Host端与Device端的USB通信速率并不受所谓的串口波特率影响,它就是标准的USB2.0全速(12Mbps)速度,实际速率取决于总线的实际使用率、驱动访问USB外设有效速率(两边)以及外部环境对通信本身造成的干扰率等等因素组成。

USB CDC类实现了规范的下列方面:
• 设备描述符管理
• 配置描述符管理
• 枚举为具有2个数据端点(IN和OUT)的CDC设备和一个指令端点(IN)
• 请求管理(如规范6.2节所述)
• 抽象控制模型兼容
• 联合体功能收集(使用1个IN端点控制)
• 数据接口类

CDC软件框架简介
当USBD设备初始化且枚举完成后,USB设备首先通过cdc_acm_check_ready()函数check是否准备数据发送,如果不需要发送就调用cdc_acm_data_receive()函数接收上位机发送的数据,如果需要发送就调用cdc_acm_data_send()将接收到的数据发送给主机。

设备描述符如下所示,其中bDevcieClass为0x02,表明当前设备为CDC设备类。

/* note:it should use the C99 standard when compiling the below codes */
/* USB standard device descriptor */
const usb_descriptor_device_struct device_descriptor =
{
    .Header = 
     {
         .bLength = USB_DEVICE_DESC_SIZE, 
         .bDescriptorType = USB_DESCTYPE_DEVICE
     },
    .bcdUSB = 0x0200,
    .bDeviceClass = 0x02,
    .bDeviceSubClass = 0x00,
    .bDeviceProtocol = 0x00,
    .bMaxPacketSize0 = USBD_EP0_MAX_SIZE,
    .idVendor = USBD_VID,
    .idProduct = USBD_PID,
    .bcdDevice = 0x0100,
    .iManufacturer = USBD_MFC_STR_IDX,
    .iProduct = USBD_PRODUCT_STR_IDX,
    .iSerialNumber = USBD_SERIAL_STR_IDX,
    .bNumberConfigurations = USBD_CFG_MAX_NUM
};

由配置描述符可知,该USB虚拟串口设备包含两个接口:CMD命令接口和data数据接口。CMD命令接口包含一个IN端点,用于传输命令,该端点采用中断传输方式,轮询间隔为10ms,最大包长为8字节。data数据接口包含一个OUT端点和一个IN端点,这两个端点均采用批量传输方式,最大包长为64字节。另外,该配置描述符中包含了一些类特殊接口描述符,具体请读者参阅CDC类标准协议。

/* USB device configuration descriptor */
usb_descriptor_configuration_set_struct configuration_descriptor = 
{
     .cdc_loopback_cmd_endpoint = 
    {
        .Header = 
         {
            .bLength = sizeof(usb_descriptor_endpoint_struct), 
            .bDescriptorType = USB_DESCTYPE_ENDPOINT
         },
        .bEndpointAddress = CDC_ACM_CMD_EP,
        .bmAttributes = 0x03,
        .wMaxPacketSize = CDC_ACM_CMD_PACKET_SIZE,
        .bInterval = 0x0A
    },

    .cdc_loopback_data_interface = 
    {
        .Header = 
         {
            .bLength = sizeof(usb_descriptor_interface_struct), 
            .bDescriptorType = USB_DESCTYPE_INTERFACE
         },
        .bInterfaceNumber = 0x01,
        .bAlternateSetting = 0x00,
        .bNumEndpoints = 0x02,
        .bInterfaceClass = 0x0A,
        .bInterfaceSubClass = 0x00,
        .bInterfaceProtocol = 0x00,
        .iInterface = 0x00
    },
     .cdc_loopback_out_endpoint = 
    {
        .Header = 
         {
             .bLength = sizeof(usb_descriptor_endpoint_struct), 
             .bDescriptorType = USB_DESCTYPE_ENDPOINT 
         },
        .bEndpointAddress = CDC_ACM_DATA_OUT_EP,
        .bmAttributes = 0x02,
        .wMaxPacketSize = CDC_ACM_DATA_PACKET_SIZE,
        .bInterval = 0x00
    },

    .cdc_loopback_in_endpoint = 
    {
        .Header = 
         {
             .bLength = sizeof(usb_descriptor_endpoint_struct), 
             .bDescriptorType = USB_DESCTYPE_ENDPOINT 
         },
        .bEndpointAddress = CDC_ACM_DATA_IN_EP,
        .bmAttributes = 0x02,
        .wMaxPacketSize = CDC_ACM_DATA_PACKET_SIZE,
        .bInterval = 0x00
    }
};

为了实现CDC设备类,设备需要支持一些设备类专用请求,这些类专用请求的处理在cdc_acm_req_handler()函数中,该函数的定义如下所示,其中SET_LINE_CODING命令用于响应主机向设备发送设备配置,包括波特率、停止位、字符位数等,收到的数据保存在noti_bu内。GET_LINE_CODING命令用于主机请求设备当前的波特率、停止位、奇偶校验位和字符位数,但在本例程中,主机并未请求该命令,所以设备所设置的串口数据并没有作用,主机可以选择任意波特率与设备进行通信。

/*!
    \brief      handle the CDC ACM class-specific requests
    \param[in]  pudev: pointer to USB device instance
    \param[in]  req: device class-specific request
    \param[out] none
    \retval     USB device operation status
*/
usbd_status_enum cdc_acm_req_handler (void *pudev, usb_device_req_struct *req)
{
    uint16_t len = CDC_ACM_DESC_SIZE;
    uint8_t  *pbuf= (uint8_t*)(&configuration_descriptor) + 9;

    switch (req->bmRequestType & USB_REQ_MASK) {
    case USB_CLASS_REQ:
        switch (req->bRequest) {
        case SEND_ENCAPSULATED_COMMAND:
            break;
        case GET_ENCAPSULATED_RESPONSE:
            break;
        case SET_COMM_FEATURE:
            break;
        case GET_COMM_FEATURE:
            break;
        case CLEAR_COMM_FEATURE:
            break;
        case SET_LINE_CODING:
            /* set the value of the current command to be processed */
            cdc_cmd = req->bRequest;
            /* enable EP0 prepare to receive command data packet */
            usbd_ep_rx (pudev, EP0_OUT, usb_cmd_buffer, req->wLength);
            break;
        case GET_LINE_CODING:
            usb_cmd_buffer[0] = (uint8_t)(linecoding.dwDTERate);
            usb_cmd_buffer[1] = (uint8_t)(linecoding.dwDTERate >> 8);
            usb_cmd_buffer[2] = (uint8_t)(linecoding.dwDTERate >> 16);
            usb_cmd_buffer[3] = (uint8_t)(linecoding.dwDTERate >> 24);
            usb_cmd_buffer[4] = linecoding.bCharFormat;
            usb_cmd_buffer[5] = linecoding.bParityType;
            usb_cmd_buffer[6] = linecoding.bDataBits;
            /* send the request data to the host */
            usbd_ep_tx (pudev, EP0_IN, usb_cmd_buffer, req->wLength);
            break;
        case SET_CONTROL_LINE_STATE:
            break;
        case SEND_BREAK:
            break;
        default:
            break;
        }
        break;
    case USB_STANDARD_REQ:
        /* standard device request */
        switch(req->bRequest) {
        case USBREQ_GET_INTERFACE:
            usbd_ep_tx(pudev, EP0_IN, (uint8_t *)&usbd_cdc_altset, 1);
            break;
        case USBREQ_SET_INTERFACE:
            if ((uint8_t)(req->wValue) < USBD_ITF_MAX_NUM) {
                usbd_cdc_altset = (uint8_t)(req->wValue);
            } else {
                /* call the error management function (command will be nacked */
                usbd_enum_error (pudev, req);
            }
            break;
        case USBREQ_GET_DESCRIPTOR:
            if(CDC_ACM_DESC_TYPE == (req->wValue >> 8)){
                len = MIN(CDC_ACM_DESC_SIZE, req->wLength);
                pbuf = (uint8_t*)(&configuration_descriptor) + 9 + (9 * USBD_ITF_MAX_NUM);
            }

            usbd_ep_tx(pudev, EP0_IN, pbuf, len);
            break;
        default:
            break;
        }
    default:
        break;
    }

    return USBD_OK;
}

数据接收通过cdc_acm_data_receive()函数实现,该函数的程序如下所示。在该函数中,首先将packet_receive标志位设置为0,表明接下来将进行接收数据,当接收完成时,在cdc_acm_data_handler ()函数中,将packet_receive标志位置1,表明数据接收完成。usbd_ep_rx()用于配置接收操作,利用CDC_OUT_EP端点,将接收到的数据放置在用户缓冲区中。

/*!
    \brief      receive CDC ACM data
    \param[in]  pudev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
void cdc_acm_data_receive(void *pudev)
{
    packet_receive = 0;

    usbd_ep_rx(pudev, CDC_ACM_DATA_OUT_EP, (uint8_t*)(usb_data_buffer), CDC_ACM_DATA_PACKET_SIZE);
}

/*!
    \brief      endpoint prepare to receive data
    \param[in]  pudev: pointer to usb core instance
    \param[in]  ep_addr: endpoint address
                  in this parameter:
                    bit0..bit6: endpoint number (0..7)
                    bit7: endpoint direction which can be IN(1) or OUT(0)
    \param[in]  pbuf: user buffer address pointer
    \param[in]  buf_len: buffer length
    \param[out] none
    \retval     none
*/
void  usbd_ep_rx (usbd_core_handle_struct *pudev, uint8_t ep_addr, uint8_t *pbuf, uint16_t buf_len)
{
    usb_ep_struct *ep;
    uint8_t ep_num = ep_addr & 0x7FU;

    ep = &pudev->out_ep[ep_num];

    /* configure the transaction level parameters */
    ep->trs_buf = pbuf;
    ep->trs_len = buf_len;

    /* enable endpoint to receive */
    USBD_ENDP_RX_STATUS_SET(ep_num, EPRX_VALID);
}
/*!
    \brief      handle CDC ACM data
    \param[in]  pudev: pointer to USB device instance
    \param[in]  rx_tx: data transfer direction:
      \arg        USBD_TX
      \arg        USBD_RX
    \param[in]  ep_id: endpoint identifier
    \param[out] none
    \retval     USB device operation status
*/
usbd_status_enum  cdc_acm_data_handler (void *pudev, usbd_dir_enum rx_tx, uint8_t ep_id)
{
    if ((USBD_TX == rx_tx) && ((CDC_ACM_DATA_IN_EP & 0x7F) == ep_id)) {
        packet_sent = 1;
        return USBD_OK;
    } else if ((USBD_RX == rx_tx) && ((EP0_OUT & 0x7F) == ep_id)) {
        cdc_acm_EP0_RxReady (pudev);
    } else if ((USBD_RX == rx_tx) && ((CDC_ACM_DATA_OUT_EP & 0x7F) == ep_id)) {
        packet_receive = 1;
        receive_length = usbd_rx_count_get(pudev, CDC_ACM_DATA_OUT_EP);
        return USBD_OK;
    }
    return USBD_FAIL;
}

数据发送通过cdc_acm_data_send()函数实现,该函数的程序如下所示。在该函数中,首先将packet_sent标志位设置为0,表明接下来将进行发送数据,当数据发送完成时,在cdc_acm_data_handler ()函数中,将packet_sent标志位设置为1,表明数据发送完成。usbd_ep_tx()用于配置发送操作,利用CDC_IN_EP端点,将以 ep->trs_buf地址为起始trs_len 长度的数据发送给主机。

void cdc_acm_data_send (void *pudev, uint32_t data_len)
{
    /* limit the transfer data length */
    if (data_len <= CDC_ACM_DATA_PACKET_SIZE) {
        packet_sent = 0;
        usbd_ep_tx(pudev, CDC_ACM_DATA_IN_EP, (uint8_t*)(usb_data_buffer), data_len);
    }
}

usbd_status_enum  cdc_acm_data_handler (void *pudev, usbd_dir_enum rx_tx, uint8_t ep_id)
{
    if ((USBD_TX == rx_tx) && ((CDC_ACM_DATA_IN_EP & 0x7F) == ep_id)) {
        packet_sent = 1;
        return USBD_OK;
    } else if ((USBD_RX == rx_tx) && ((EP0_OUT & 0x7F) == ep_id)) {
        cdc_acm_EP0_RxReady (pudev);
    } else if ((USBD_RX == rx_tx) && ((CDC_ACM_DATA_OUT_EP & 0x7F) == ep_id)) {
        packet_receive = 1;
        receive_length = usbd_rx_count_get(pudev, CDC_ACM_DATA_OUT_EP);
        return USBD_OK;
    }
    return USBD_FAIL;
}
void  usbd_ep_tx (usbd_core_handle_struct *pudev, uint8_t ep_addr, uint8_t *pbuf, uint16_t buf_len)
{
    __IO uint32_t len = 0U;
    uint8_t ep_num = ep_addr & 0x7FU;
    usb_ep_struct *ep = &pudev->in_ep[ep_num];

    /* configure the transaction level parameters */
    ep->trs_buf = pbuf;
    ep->trs_len = buf_len;
    ep->trs_count = 0U;

    /* transmit length is more than one packet */
    if (ep->trs_len > ep->maxpacket) {
        len = ep->maxpacket;
    } else {
        len = ep->trs_len;
    }

    usbd_ep_data_write(ep->trs_buf, (pbuf_reg + ep_num)->tx_addr, (uint16_t)len);
    (pbuf_reg + ep_num)->tx_count = (uint16_t)len;

    /* enable endpoint to transmit */
    USBD_ENDP_TX_STATUS_SET(ep_num, EPTX_VALID);
}

六.配置一个USB虚拟串口收发例程

STLINK接GD32F103C8T6开发板,STLINK接电脑USB口。

在这里插入图片描述

主要代码

#include "gd32f10x.h"
#include "gd32f10x_libopt.h"
#include "systick.h"
#include "cdc_acm_core.h"

extern uint8_t packet_sent, packet_receive;
extern uint32_t receive_length;
extern uint8_t usb_data_buffer[CDC_ACM_DATA_PACKET_SIZE];

//定义 CDC-ACM 类的 USB 设备
usbd_core_handle_struct  usb_device_dev = 
{
    .dev_desc = (uint8_t *)&device_descriptor,
    .config_desc = (uint8_t *)&configuration_descriptor,
    .strings = usbd_strings,
    .class_init = cdc_acm_init,
    .class_deinit = cdc_acm_deinit,
    .class_req_handler = cdc_acm_req_handler,
    .class_data_handler = cdc_acm_data_handler
};

//中断使能
void nvic_config(void)
{
    /* 1 bit for pre-emption priority, 3 bits for subpriority */
    nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3);

    /* enable the USB low priority interrupt */
    nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn, 1, 0);
}
int main(void)
{
	rcu_apb2_clock_config(RCU_APB2_CKAHB_DIV1);//设置主频72M(#define __SYSTEM_CLOCK_72M_PLL_HXTAL         (uint32_t)(72000000)),8M外部晶振  (#define HXTAL_VALUE    ((uint32_t)8000000))
  systick_config();//配置1ms SysTick
	rcu_periph_clock_enable(RCU_GPIOA);
	rcu_periph_clock_enable(RCU_AF);//AF时钟使能 


	/* configure USB model clock from PLL clock */
	rcu_usb_clock_config(RCU_CKUSB_CKPLL_DIV1_5);//72M主频,1.5分频,就是48M

	/* enable USB APB1 clock */
	rcu_periph_clock_enable(RCU_USBD);

	/* USB device configuration */
	usbd_core_init(&usb_device_dev);

	/* NVIC configuration */
	nvic_config();

	/* now the usb device is connected */
	usb_device_dev.status = USBD_CONNECTED;

    while (1)
    {
        if (USBD_CONFIGURED == usb_device_dev.status) {
            if (1 == packet_receive && 1 == packet_sent) {//有数据接收到
                packet_sent = 0;
                /* receive datas from the host when the last packet datas have sent to the host */
                cdc_acm_data_receive(&usb_device_dev);//接收数据
            } else {
                if (0 != receive_length) {
                    /* send receive datas */
                    cdc_acm_data_send(&usb_device_dev, receive_length);//发送接收到的数据
                    receive_length = 0;
                }
            }
        }
    }
 
}

实验效果
下载完程序,用 USB 线接板子USB 口,再接电脑,打开电脑上串口调试器,9600 波特率,8 位数据,无校验,发送 HELLOWORLD,板子就会返回 HELLOWORLD。
在这里插入图片描述

七.工程源代码下载

通过网盘分享的文件:27.USB虚拟串口通信实验.zip
链接: https://pan.baidu.com/s/1MDB8vX7EtFcuV_LcKmBh_Q 提取码: cp9x

如果链接失效,可以联系博主给最新链接
程序下载下来之后解压就行

八.小结

USB虚拟串口可以实现与电脑的通信,还可以实现与外部设备的通信,广泛应用于工业控制、智能家居、智能硬件等领域。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2134928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

android10 系统定制:增加应用使用数据埋点,应用使用时长统计

需求意在统计应用的使用时长和开始结束时间,最终生成一个文件可以直观看出什么时候进入了哪个应用、什么时候退出,如图: 每行记录了应用的进入或退出,以逗号分割。分别记录了事件开始时间,应用包名,进入或退出(1或2),应用名称。 根据上面的数据记录可以看出:2024-08…

B2C电商接口解决方案||搭建电商项目必备电商接口

01 涉及接口 电商接口&#xff1a;天猫、京东商城、拼多多、有赞、快递鸟接口。 02 业务场景 企业在第三方电商平台开店&#xff0c;需要对电商平台上商品价格、库存进行更新&#xff0c;并将电商订单下载到ERP系统&#xff0c;ERP进行作业后&#xff0c;将物流信息上传到电…

【MATLAB源码-第265期】基于simulink的DQPSK调制解调系统仿真,未直接调用模块并且手动实现,输出各节点波形图。

操作环境&#xff1a; MATLAB 2013a / 2013b 1、算法描述 差分正交相移键控&#xff08;DQPSK&#xff09;调制解调系统是一种非常成熟的数字通信技术&#xff0c;主要用于传输数据中的相位变化&#xff0c;而非绝对相位。这一特性使得它对信号中的相位抖动和其他干扰具有更…

再次进阶 舞台王者 第八季完美童模全球赛见证官【孔翊橙】赛场+秀场超燃合集!

7月20-23日&#xff0c;2024第八季完美童模全球总决赛在青岛圆满落幕。在盛大的颁奖典礼上&#xff0c;一位才能出众的少女——孔翊橙迎来了她舞台生涯的璀璨时刻。见证官——孔翊橙&#xff0c;以璀璨童星之姿&#xff0c;优雅地踏上完美童模盛宴的绚丽舞台&#xff0c;作为开…

双副本与双活:TDengine 为企业打造的高效数据管理利器

在现代数据管理中&#xff0c;企业对于可靠性、可用性和成本的平衡有着多样化的需求。为此&#xff0c;TDengine 在 3.3.0.0 版本中推出了两种不同的企业级解决方案&#xff1a;双活方案和基于仲裁者的双副本方案&#xff0c;以满足不同应用场景下的特殊需求。本文将详细探讨这…

看看智慧门诊银医通自助服务方案,如何化解医院患者跑难题

“看病三分钟&#xff0c;排队三小时”&#xff0c;这是许多患者在就医过程中的无奈吐槽。挂号队伍长如龙&#xff0c;看病流程繁琐复杂&#xff0c;缴费窗口人满为患&#xff0c;检查报告等待时间漫长…… 这些就医痛点&#xff0c;不仅让患者身心疲惫&#xff0c;也给医院的管…

Python编码系列—Python抽象工厂模式:构建复杂对象家族的蓝图

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

wifi MSDU MPDU BA

MSDU (Mac service data unit) mac 服务层 数据单元&#xff0c; 也就是包含了 mac 层头和 FCS 的 802.11 包。 MPDU&#xff08;Mac protocol data unit&#xff09;mac 协议层数据单元&#xff0c; 这个包只是原始的802.11 数据包 在新的wifi 协议中有两种聚合方式 A-MSDU…

数仓建设:为什么我们的数据容易被业务方质疑?

目录 0 问题背景 1 问题产生的原因 2 问题解决方案 3 小结 数字化建设通关指南专栏原价99&#xff0c;现在活动价39.9&#xff0c;按照阶梯式增长&#xff0c;直到恢复原价 0 问题背景 “ 在数字化建设进程中&#xff0c;无论是处于数据产品的你&#xff0c;或是数据开发的…

ModbusTCP/RTU转Ethernet/IP(CIP)-Modbus设备与罗克韦尔AB的PLC之间通讯

IGT-DSER智能网关模块支持西门子、三菱、欧姆龙、罗克韦尔AB等各种品牌的PLC之间通讯&#xff0c;同时也支持PLC与Modbus协议的工业机器人、智能仪表、变频器等设备通讯。网关有多个网口、串口&#xff0c;也可选择WIFI无线通讯。无需PLC内编程开发&#xff0c;只要在IGT-DSER智…

Python 数学建模——Vikor 多标准决策方法

文章目录 前言原理步骤代码实例 前言 Vikor 归根到底其实属于一种综合评价方法。说到综合评价方法&#xff0c;TOPSIS&#xff08;结合熵权法使用&#xff09;、灰色关联度分析、秩和比法等方法你应该耳熟能详。Vikor 未必比这些方法更出色&#xff0c;但是可以拓展我们的视野。…

从GreaterWMS学习仓库管理系统

前言 客户并不知道&#xff08;确切地&#xff09;他们需要什么&#xff1f; 需要通过需求分析工具和技术&#xff0c;利用宽进严出的需求池&#xff0c;需求验证使用原型测试&#xff0c;场景分析&#xff0c;专家评审&#xff0c;交叉检查等手段&#xff0c;经过充分验证的需…

牛耕分解+形态学分割 全覆盖路径规划(二)Part1. 分割

书接上文&#xff1a;牛耕分解形态学分割 全覆盖路径规划&#xff08;一&#xff09; 前置文章1&#xff1a;房屋区域分割算法 Morphological Segmentation 前置文章2&#xff1a;牛耕覆盖算法 Boustrophedon Coverage Path Planning 项目地址&#xff1a;ipa320 / ipa_cove…

基于Kubernetes部署Spark:spark on kubernetes

什么是spark&#xff1f; spark是一种基于内存的快速、通用、可扩展的的数据分析计算引擎。 Hadoop、Hive、Spark是什么关系&#xff1f; 大数据技术生态中&#xff0c;Hadoop、Hive、Spark是什么关系&#xff1f;| 通俗易懂科普向_哔哩哔哩_bilibili Hadoop 与 HDFS (Hado…

【腾讯云】AI驱动TDSQL-C Serveress 数据库技术实战营-如何是从0到1体验电商可视化分析小助手得统计功能,一句话就能输出目标统计图

欢迎来到《小5讲堂》 这是《腾讯云》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景效果图流程图创建数据库基本信息数据库配置设置密码控制台开启…

腾讯联合多所高校出品!AI游戏生成模型 GameGen-O 轻松生成高质量游戏元素

最近&#xff0c;香港和中国的几所大学与腾讯联合研发了一个名为 GameGen-O 的 AI 模型&#xff0c;专门用于创建开放世界视频游戏的模拟。 GameGen-O 的功能不仅仅限于生成高质量的游戏内容&#xff0c;它还模拟了多种游戏引擎的特性&#xff0c;能够创造出丰富多样的游戏元素…

字符分类函数和字符串函数

Hello~,欢迎大家来到我的博客进行学习&#xff01; 目录 1.字符分类函数&#x1f618;1.1解释以及使用&#x1f92f;1.2将字符分类函数组合使用&#x1f47b;2 .字符转换函数&#x1f9d0; 1.字符分类函数&#x1f618; 1.1解释以及使用&#x1f92f; 在键盘上敲的字符有许多…

揭秘高效日志管理:解锁数据宝藏,驱动业务精准决策

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言&#…

duckdb 连接postgres 和 jdbc 的使用

why&#xff1f; 主要是特别快 嵌入式&#xff0c;不需要服务器&#xff0c;使用超级方便 扩展机制灵活&#xff0c;可以直接读取CSV、JSON、Parquet等文件 Parquet文件格式详解&#xff08;含行、列式存储区别&#xff09;_parquet格式-CSDN博客 采用列式存储&#xff08;用…

边缘计算网关:连接中心计算与边缘设备的重要桥梁-天拓四方

一、边缘计算网关&#xff1a;重新定义信息高速公路的“路标” 边缘计算网关&#xff0c;作为边缘计算生态系统中的核心组件&#xff0c;不仅承载着数据传输的功能&#xff0c;更是智能信息处理的关键节点。它通过分布式计算架构&#xff0c;将数据处理任务前置到网络边缘&…