• [技术干货] 小熊派低功耗串口接受数据异常
    低功耗串口接受数据异常 一、异常信息 1.1 硬件环境 1.2 软件环境 1.3 问题描述 二、问题分析 三、解决方式 小熊派低功耗串口接受数据异常一、异常信息1.1 硬件环境小熊派 STM32L4 单片机1.2 软件环境STM32CubeMX 6.2.1MDK 5.31.3 问题描述使用 STM32CubeMX 配置 STM32 的 LPUART 后,生成代码使用串口 DMA 接受数据,在开启数据接受后,指定接受的数据长度,接受完成后,串口继续接受数据,在开启下一个串口接受时则会读取之前的数据,例如:串口使能接受 5 个数据,然后上位机发送 ABCDEFG,本次串口 DMA 在接受到 ABCDE 时就会产生接受完成标志,调用回调函数,至于 FG 则不会接受丢弃掉,但我如果在设置接受 2 个数据,这两个数据居然还能接受到在仿真调试里面运行程序时这两个数据不会接受到,很正常,符合逻辑,但一旦实际下载到单片机里面实际运行就会出现错误二、问题分析根据实验现象,仿真是很正常的,但实际下载运行就会出现问题,推测编译器或者生成的代码有问题理论上第一次接受数量达标后,代码会将接受完成标志置位,然后串口的缓存区不会再接受任何数据,出现这种情况的原因可能是接受完成后数据 DMA 和串口没有关闭三、解决方式在调用之前我们将 DMA 的缓存区清空即可,例如我下面的代码,使用 memset 清空缓存区
  • [交流分享] 聊聊万物互联-Wi-Fi6
    WiFi的历史从802.11的FHFS,DSSS到802.11b的DSSS,到802.11a的OFDM,802.11g的ERP(将OFDM从5G迁移到了2.4G),到802.11n的更宽频带(40MHz)的OFDM技术,到802.11ac的进一步拓宽(80, 80+80,160MHz)的OFDM技术,到802.11ax的更窄的子载波(78.125kHz)的OFDM技术,到讨论中的802.11be的320MHz的OFDM技术。OFDM技术从802.11a,大概2001年的时候,到现在,已经持续演进了20年了,它的抗多径效应,适合无线空间的复杂环境。因为802.11ax(Wi-FI 6)希望覆盖更广的空间,所以把载波宽度进一步变小了。每一代的发展,频带基本是越来越宽,似乎是可以无限的把频宽扩展下去。但是这应该是存在问题的,多样化的需求下,大频宽是可能浪费频段的。毕竟有些地方只需要小的频宽就好了。802.11ax还定义了一个仅支持20MHz的模式,也是瞄准了万物互联的趋势下,小数据,低能耗的搭配。但是802.11be(TB Wi-Fi 7)又把带宽变得更大了,每一代总是希望能更快的。但是单纯更快有什么意思呢。OFDM不止可以分频带,因为网络的特性,还可以分用户,这是Wi-Fi现在越来越看重的,希望越来越多的用户都是使用这个网络,而且还要能用。从802.11ac(2013)开始的Wi-Fi5已经引入了多用户的观念,对应的技术是MU-MIMO。它是这么样的,不同的用户用的是不同的天线。到了802.1ax(Wi-Fi6),支持上行的MU-MIMO,而且还把一个频段同时分配给了多个用户(OFDMA),每个用户最少可以只用2MHz的带宽。一下还把上行和下行都给加进去了。不止如此,还加入了BSS Coloring技术,弱化Exposed Node的问题,提升密集网络覆盖下的并行性。Wi-Fi6有这么多优势,其实它的主要着笔点在于密集用户,密集网络。想想现在Wi-Fi6的设备已经都出来1两年了,但是也并没有多改变生态。其实现在大家也觉得够用了,就像我自己,这种Wi-Fi6的理念对人这个用户来讲真的是有点超前了,哪里去找如此密集的人流和网络?办公场所,体育场?这些场合毕竟是少数。自动化生产车间也许是比较合适的,不过不是人流,而是物流。Wi-Fi7似乎要在这个道路上越走越远,越来越远…
  • [Atlas 200] linux spi驱动分析-1
    # 准备   使用的源码包为华为官方的ascend200AI加速模块的SDK,其下载地址位于:[点击跳转](https://www.hiascend.com/hardware/firmware-drivers?tag=community)   使用的固件与驱动版本为:1.0.9.alpha   压缩包名称为:A200-3000-sdk_20.2.0.zip   将A200-3000-sdk_20.2.0.zip解压后可以看到Ascend310-source-minirc.tar.gz压缩包,这个压缩包里有ascend200AI加速模块的linux内核源码包、设备树及驱动文件等。 # 设备树节点和驱动的匹配   这里不做过多的赘述了,可自行百度。   其spi的设备树节点位于source/dtb/hi1910-fpga-spi.dtsi中,内容如下: ```bash alg_clk: alg_clk { compatible = "fixed-clock"; #clock-cells = 0>; clock-frequency = 200000000>; }; spi_0: spi@130980000{ #address-cells = 1>; #size-cells = 0>; compatible = "hisi-spi"; reg = 0x1 0x30980000 0 0x10000>, 0x1 0x30900000 0 0x1000>; interrupts = 0 322 4>; clocks = &alg_clk>; clock-names = "spi_clk"; num-cs = 2>; id = 0>; status = "ok"; }; ```   spi_0和spi_1节点的compatible 属性会和spi控制器的驱动匹配。(位于source/drivers/dev_plat/spi/spi-hisi.c) ```bash STATIC const struct of_device_id hisi_spi_dt_ids[] = { { .compatible = "hisi-spi" }, { /* sentinel */ } }; ``` ```bash STATIC struct platform_driver hisi_spi_driver = { .driver = { .name = "hisi_spi", .pm = HISI_SPI_PM_OPS, .of_match_table = of_match_ptr(hisi_spi_dt_ids), #ifdef CONFIG_ACPI .acpi_match_table = ACPI_PTR(hisi_spi_acpi_ids), #endif }, .probe = hisi_spi_probe, .remove = hisi_spi_remove, }; ```   内核会先解析设备树节点,然后驱动程序注册的时候of_match_ptr中的of_device_id会和设备树节点进行匹配。 # spi控制器驱动分析(source/dev_plat/spi/spi-hisi.c)   spi_0和spi_1的设备树节点都会匹配上spi控制器驱动,然后执行他的probe函数,由于spi_1节点的状态为disable,所以只有spi_0节点会执行一次probe函数 ```bash int ret; int irq; u32 id = 0; u32 cs_num = 0; u32 rate = 0; struct resource *res = NULL; struct resource *res_iomux = NULL; struct hisi_spi *hs = NULL; struct spi_master *master = NULL; ```   初始化变量 ```bash ASSERT_RET((pdev != NULL), -EINVAL); dev_info(&pdev->dev, "hi_spi_probe enter..\n"); dev_info(&pdev->dev, "hi:dev name %s, id %d, auto %d, num res %u\n", pdev->name, pdev->id, pdev->id_auto, pdev->num_resources); ```   ASSERT_RET断言,判断platform_device *pdev结构体是否为空。 dev_info和printk类似,在内核启动时打印消息 ```c ret = hisi_spi_get_dts(pdev, &cs_num, &id, &rate); if (ret != 0) { dev_err(&pdev->dev, "hisi_spi_get_dts fail\n"); return -EINVAL; } ```   hisi_spi_get_dts用于获取片选和时钟等信息 ```c STATIC int hisi_spi_get_dts(struct platform_device *pdev, u32 *cs_num, u32 *id, u32 *rate) { int ret; #ifndef CONFIG_ACPI struct clk *clk = NULL; #endif if ((pdev == NULL) || (cs_num == NULL) || (id == NULL) || (rate == NULL)) { return -EINVAL; } #ifdef CONFIG_ACPI ret = device_property_read_u32(&pdev->dev, "num-cs", cs_num); if (ret 0) { dev_err(&pdev->dev, "get num_cs error: %d \n", ret); return -EINVAL; } ret = device_property_read_u32(&pdev->dev, "id", id); if (ret 0) { dev_err(&pdev->dev, "get id error: %d \n", ret); return -EINVAL; } ret = device_property_read_u32(&pdev->dev, "spi_clk", rate); if (ret 0) { dev_err(&pdev->dev, "get clk error : %d\n", ret); return -EINVAL; } #else ret = of_property_read_u32(pdev->dev.of_node, "num-cs", cs_num); if (ret != 0) { dev_err(&pdev->dev, "of_property_read_u32 get num-cs fail\n"); return -EINVAL; } ret = of_property_read_u32(pdev->dev.of_node, "id", id); if (ret != 0) { dev_err(&pdev->dev, "of_property_read_u32 get id fail\n"); return -EINVAL; } clk = clk_get(&pdev->dev, "spi_clk"); if (IS_ERR(clk)) { return PTR_ERR(clk); } *rate = clk_get_rate(clk); clk_put(clk); #endif return 0; } ```   这个ACPI好像和电源管理有关,内核中全局搜索CONFIG_ACPI也未找到定义,所里这里先不管他。   这个hisi_spi_get_dts会获取设备书中spi节点信息   片选数量cs_num=2   spi节点id=0   spi时钟速率rate = 200000000   接着获取设备树描述中的mem资源 ```c /* get base addr */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (unlikely(res == NULL)) { dev_err(&pdev->dev, "invalid resource\n"); return -EINVAL; } ```   这里要先说一下platform_get_resource这个函数,内核函数定义如下: ```c struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num) { int i; for (i = 0; i dev->num_resources; i++) { struct resource *r = &dev->resource[i]; if (type == resource_type(r) && num-- == 0) return r; } return NULL; } ```   从第一份资源开始匹配,type用来匹配资源类型num--==0表示你想获得的是第多少份资源   spi0设备树节点: ```c reg = 0x1 0x30980000 0 0x10000>, 0x1 0x30900000 0 0x1000>; interrupts = 0 322 4>; ```   则spi0的pdev结构体的资源有三个,两个为IORESOURCE_MEM,一个为IORESOURCE_IRQ。这里调用: ```bash res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ```   获取的是0x1 0x30980000 0 0x10000>他表示spi控制器的内存起始地址为0x30980000 ,大小为0x10000。然后获取irq资源: ```c irq = platform_get_irq(pdev, 0); if (irq 0) { dev_err(&pdev->dev, "platform_get_irq error\n"); return -ENODEV; } dev_info(&pdev->dev, "hi: get irq %d\n", irq); ```   中断代码中我没有使用过,所以不管他了,接下来是分配spi_master结构体的内存: ```c master = spi_alloc_master(&pdev->dev, sizeof(*hs)); if (master == NULL) { dev_err(&pdev->dev, "spi_alloc_master error.\n"); return -ENOMEM; } ```   跳入spi_alloc_master里面: ```c static inline struct spi_controller *spi_alloc_master(struct device *host, unsigned int size) { return __spi_alloc_controller(host, size, false); } ``` ```c struct spi_controller *__spi_alloc_controller(struct device *dev, unsigned int size, bool slave) { struct spi_controller *ctlr; if (!dev) return NULL; ctlr = kzalloc(size + sizeof(*ctlr), GFP_KERNEL); if (!ctlr) return NULL; device_initialize(&ctlr->dev); ctlr->bus_num = -1; ctlr->num_chipselect = 1; ctlr->slave = slave; if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave) ctlr->dev.class = &spi_slave_class; else ctlr->dev.class = &spi_master_class; ctlr->dev.parent = dev; pm_suspend_ignore_children(&ctlr->dev, true); spi_controller_set_devdata(ctlr, &ctlr[1]); return ctlr; } EXPORT_SYMBOL_GPL(__spi_alloc_controller); ```   重点关注这两行: ```c ctlr = kzalloc(size + sizeof(*ctlr), GFP_KERNEL); spi_controller_set_devdata(ctlr, &ctlr[1]); ```   总的来说这个spi_alloc_master函数初始化了spi_controller结构体的dev和各个参数,将其设置为master而不是slave,分配的是spi_controller结构体加上自定义的hisi_spi结构体大小,其中master->dev->driver_data指针指向后面这片大小为hisi_spi结构体的内存,hisi_spi是各个厂家根据自己的控制器驱动自定义的结构体。然后接下来填充spi_master结构体 ```c /* the spi->mode bits understood by this driver: */ master->mode_bits = SPI_CPHA | SPI_CPOL; master->dev.of_node = pdev->dev.of_node; master->bus_num = pdev->id; master->num_chipselect = cs_num; master->setup = hisi_spi_setup; master->transfer_one_message = hisi_spi_transfer_one_message; master->cleanup = hisi_spi_cleanup; ```   spi模式默认使用SPI_MODE_3,即clock信号默认为高电平,data信号在clock的第二个跳变沿解析数据。我们使用的是spi0,他的id为0,所以这里的bus_num为0,num_chipselect 为2   hisi_spi_transfer_one_message这个函数,这是spi收发数据的函数。具体在下面讲。 ```c platform_set_drvdata(pdev, master); ```   master是经过上面的spi_alloc_master函数malloc出来的地址,经过platform_set_drvdata后pdev->dev->driver_data指向这片地址,需要的时候可以使用platform_get_drvdata在拿出来使用 ```c hs = spi_master_get_devdata(master); ASSERT_RET((hs != NULL), -ENODEV); ```   spi_master_get_devdata这个函数就是我们上面提到过的,spi_alloc_master分配的除了spi_controller 以外的大小为hisi_spi结构体的大小。 ```c spin_lock_init(&hs->lock); hs->flags = 0; hs->phybase = res->start; hs->pdev = pdev; hs->spi_id = id; hs->clk_rate = (unsigned long)rate; hs->n_bytes = 1; hs->dfs = 8; hs->regs = ioremap(res->start, resource_size(res)); if (hs->regs == NULL) { dev_err(&pdev->dev, "ioremap error.\n"); ret = -ENOMEM; goto out_ioremap; } hs->irq = irq; ret = request_irq(irq, hisi_spi_interrupt, 0, dev_name(&pdev->dev), master); if (ret) { dev_err(&pdev->dev, "request_irq error %d.\n", ret); goto out_irq; } dev_info(&pdev->dev, "hi: get clk rate %d\n", rate); ```   填充hisi_spi结构体,初始化自旋锁,设备clk_rate,和寄存器地址等内容。 ```c res_iomux = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (res_iomux != NULL) { ret = hisi_spi_iomux_init(hs, res_iomux->start); if (ret) { dev_err(&pdev->dev, "hi_spi_iomux_init error %d.\n", ret); goto out_irq; } } ```   res_iomux获取的是第2个IORESOURCE_MEM资源,即设备树中的0x1 0x30900000 0 0x1000>,然后执行hisi_spi_iomux_init函数来初始化spi的复用功能 ```c STATIC int hisi_spi_iomux_init(struct hisi_spi *hs, unsigned long addr) { void __iomem *regs = NULL; regs = ioremap(addr, IO_MUX_MEM_REMAP_SIZE); if (regs == NULL) { dev_err(&hs->pdev->dev, "remap mem failed.\n"); return -EBUSY; } if (hs->spi_id == 0) { writel_relaxed(FUNC_CTRL_REG_SEL_DS1, regs + SPI1_CLK_FUNC_CTRL_REG); } else if (hs->spi_id == 1) { writel_relaxed(FUNC_CTRL_REG_SEL_DS1, regs + SPI2_CLK_FUNC_CTRL_REG); } else if (hs->spi_id == 2) { writel_relaxed(IOMUX_REG_SEL_SPI2, regs + IOMUX_REG_SPI2_CLK); writel_relaxed(IOMUX_REG_SEL_SPI2, regs + IOMUX_REG_SPI2_CSN); writel_relaxed(IOMUX_REG_SEL_SPI2, regs + IOMUX_REG_SPI2_MOSI); writel_relaxed(IOMUX_REG_SEL_SPI2, regs + IOMUX_REG_SPI2_MISO); writel_relaxed(FUNC_CTRL_REG_SEL_DS2 | FUNC_CTRL_REG_SEL_ST, regs + SCL2_FUNC_CTRL_REG); writel_relaxed(FUNC_CTRL_REG_SEL_DS0 | FUNC_CTRL_REG_SEL_ST | FUNC_CTRL_REG_SEL_RESERVE1 | FUNC_CTRL_REG_SEL_PD | FUNC_CTRL_REG_SEL_PU, regs + SDA2_FUNC_CTRL_REG); writel_relaxed(FUNC_CTRL_REG_SEL_DS0 | FUNC_CTRL_REG_SEL_ST | FUNC_CTRL_REG_SEL_RESERVE1 | FUNC_CTRL_REG_SEL_PD | FUNC_CTRL_REG_SEL_PU, regs + UART1_RXD_FUNC_CTRL_REG); writel_relaxed(FUNC_CTRL_REG_SEL_DS1, regs + UART1_TXD_FUNC_CTRL_REG); } else { dev_err(&hs->pdev->dev, "invalid spi id %d\n", hs->spi_id); } iounmap(regs); regs = NULL; return 0; } ```   由于我们spi_1是disable,只使用了spi_0,所以这个函数主要是执行了下面这一句 ```c writel_relaxed(FUNC_CTRL_REG_SEL_DS1, regs + SPI1_CLK_FUNC_CTRL_REG); //spi-hisi.h #define FUNC_CTRL_REG_SEL_DS1 (1 5) #define SPI1_CLK_FUNC_CTRL_REG 0x814 ```   就是把SPI1_CLK_FUNC_CTRL_REG 寄存器的第五位置了1,这里应该是他的复用功能寄存器复用为了spi。 ```c ret = hisi_spi_hw_init(hs); if (ret) { dev_err(&pdev->dev, "hisi_spi_hw_init error %d.\n", ret); goto out_spi; } ``` ```c STATIC int hisi_spi_hw_init(struct hisi_spi *hs) { u32 val; hisi_spi_disable_chip(hs); /* RX_SAMPLE_DLYΪ2 */ spi_writel(hs, RX_SAMPLE_DLY, RXD_DELAY_CYCLE); spi_writel(hs, TXFTLR, 63); spi_writel(hs, RXFTLR, 63); val = 0x3a; spi_writel(hs, IMR, val); hisi_spi_enable_chip(hs); return 0; } ```   然后是初始化spi控制器的寄存器,SPI_SSIENR(0x0008)好像是他所有的spi控制器的使能寄存器,初始化之前要写0disable,完成后在写1enable。然后对输入采样的延时周期设置,然后是对发送和接收fifo的设置,都设置为63个字节。(怪不得当时使用的时候发现每次传输超过64个字节cs就自动拉高了,用示波器点了好久,原来问题在这里,这里可能和他们硬件有关,fpga写外设时序的时候就给发送和接受缓冲区分配了63个字节),然后是设置中断屏蔽寄存器,也先不管他。 ```c ret = spi_register_master(master); if (ret) { dev_err(&pdev->dev, "spi_register_master error %d.\n", ret); goto out_spi; } ```   spi_register_master这个函数我没有看很明白,但是他是spi控制器驱动必须的,好像只要功能是向linux内核注册这个控制器,让设备驱动能找到这个控制器,经过这个函数后就能在/sys/bus/spi/device目录下找到设备以及他的子节点了。我们可以跳进去简单的看一下 ```c int spi_register_controller(struct spi_controller *ctlr) { 。。。 。。。 /* register the device, then userspace will see it. * registration fails if the bus ID is in use. */ dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num); status = device_add(&ctlr->dev); if (status 0) { /* free bus id */ mutex_lock(&board_lock); idr_remove(&spi_master_idr, ctlr->bus_num); mutex_unlock(&board_lock); goto done; } dev_dbg(dev, "registered %s %s\n", spi_controller_is_slave(ctlr) ? "slave" : "master", dev_name(&ctlr->dev)); /* * If we're using a queued driver, start the queue. Note that we don't * need the queueing logic if the driver is only supporting high-level * memory operations. */ if (ctlr->transfer) { dev_info(dev, "controller is unqueued, this is deprecated\n"); } else if (ctlr->transfer_one || ctlr->transfer_one_message) { status = spi_controller_initialize_queue(ctlr); if (status) { device_del(&ctlr->dev); /* free bus id */ mutex_lock(&board_lock); idr_remove(&spi_master_idr, ctlr->bus_num); mutex_unlock(&board_lock); goto done; } } /* add statistics */ spin_lock_init(&ctlr->statistics.lock); mutex_lock(&board_lock); list_add_tail(&ctlr->list, &spi_controller_list); list_for_each_entry(bi, &board_list, list) spi_match_controller_to_boardinfo(ctlr, &bi->board_info); mutex_unlock(&board_lock); /* Register devices from the device tree and ACPI */ of_register_spi_devices(ctlr); acpi_register_spi_devices(ctlr); done: return status; } ```   首先spi_controller_check_ops、spi_controller_is_slave检查传入的spi_controller 结构体是否设置正确,是否设置transfer、transfer_one_message等函数,重点关注下spi_controller_initialize_queue和of_register_spi_devices这两个函数: ```c static int spi_controller_initialize_queue(struct spi_controller *ctlr) { int ret; ctlr->transfer = spi_queued_transfer; if (!ctlr->transfer_one_message) ctlr->transfer_one_message = spi_transfer_one_message; /* Initialize and start queue */ ret = spi_init_queue(ctlr); if (ret) { dev_err(&ctlr->dev, "problem initializing queue\n"); goto err_init_queue; } ctlr->queued = true; ret = spi_start_queue(ctlr); if (ret) { dev_err(&ctlr->dev, "problem starting queue\n"); goto err_start_queue; } return 0; err_start_queue: spi_destroy_queue(ctlr); err_init_queue: return ret; } ```   将spi_controller的transfer 函数设置为spi_queued_transfer,下面在一开始的probe将transfer_one_message设置为了hisi_spi_transfer_one_message,所以不用管他。spi_init_queue函数初始化并且建立了一个工作线程,并且给该线程指定了一个工作函数spi_pump_messages,spi_start_queue将线程工作标注running置为true。   在spi的设备驱动中会调用master->transfer,则spi_queued_transfer函数会被调用,spi_queued_transfer将设备驱动封装的spi_message加入到master的queue链表中,然后唤醒工作函数,执行spi_pump_messages,在该函数中会执行: ```c ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg); ```   即调用了前面设置过的hisi_spi_transfer_one_message函数进行数据传输。 ```c //spi_register_master->spi_register_controller->of_register_spi_devices static void of_register_spi_devices(struct spi_controller *ctlr) { struct spi_device *spi; struct device_node *nc; if (!ctlr->dev.of_node) return; for_each_available_child_of_node(ctlr->dev.of_node, nc) { if (of_node_test_and_set_flag(nc, OF_POPULATED)) continue; spi = of_register_spi_device(ctlr, nc); if (IS_ERR(spi)) { dev_warn(&ctlr->dev, "Failed to create SPI device for %pOF\n", nc); of_node_clear_flag(nc, OF_POPULATED); } } } #else static void of_register_spi_devices(struct spi_controller *ctlr) { } #endif ```   这个for_each_available_child_of_node函数我跳进去大概看了一下。 ```c #define for_each_available_child_of_node(parent, child) \ for (child = of_get_next_available_child(parent, NULL); child != NULL; \ child = of_get_next_available_child(parent, child)) struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev) { struct device_node *next; unsigned long flags; if (!node) return NULL; raw_spin_lock_irqsave(&devtree_lock, flags); next = prev ? prev->sibling : node->child; for (; next; next = next->sibling) { if (!__of_device_is_available(next)) continue; if (of_node_get(next)) break; } of_node_put(prev); raw_spin_unlock_irqrestore(&devtree_lock, flags); return next; } EXPORT_SYMBOL(of_get_next_available_child); static bool __of_device_is_available(const struct device_node *device) { const char *status; int statlen; if (!device) return false; status = __of_get_property(device, "status", &statlen); if (status == NULL) return true; if (statlen > 0) { if (!strcmp(status, "okay") || !strcmp(status, "ok")) return true; } return false; } ```   parent代表的是当前节点,child = of_get_next_available_child(parent, NULL);表示的是第一个子节点,child = of_get_next_available_child(parent, child)表示的是当前子节点的下一个子节点,直到子节点为NULL。__of_device_is_available的意思是如果status属性为空则返回true,即默认该节点是开启的,如果有status,且他的属性是okay或者ok,则也返回ture。否则返回false。那么最上面的for_each_available_child_of_node意思就好理解了。会遍历spi节点下能用的子节点,然后执行of_register_spi_device ```c static struct spi_device * of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc) { struct spi_device *spi; int rc; /* Alloc an spi_device */ spi = spi_alloc_device(ctlr); if (!spi) { dev_err(&ctlr->dev, "spi_device alloc error for %pOF\n", nc); rc = -ENOMEM; goto err_out; } /* Select device driver */ rc = of_modalias_node(nc, spi->modalias, sizeof(spi->modalias)); if (rc 0) { dev_err(&ctlr->dev, "cannot find modalias for %pOF\n", nc); goto err_out; } rc = of_spi_parse_dt(ctlr, spi, nc); if (rc) goto err_out; /* Store a pointer to the node in the device structure */ of_node_get(nc); spi->dev.of_node = nc; /* Register the new device */ rc = spi_add_device(spi); if (rc) { dev_err(&ctlr->dev, "spi_device register error %pOF\n", nc); goto err_of_node_put; } return spi; err_of_node_put: of_node_put(nc); err_out: spi_dev_put(spi); return ERR_PTR(rc); } ```   spi_alloc_device在内核中申请spi_device的内存,of_modalias_node函数将子节点的compatible属性strlcpy 给spi->modalias,of_spi_parse_dt这个函数里面有地方需要关注下: ```c /* Device address */ rc = of_property_read_u32(nc, "reg", &value); if (rc) { dev_err(&ctlr->dev, "%pOF has no valid 'reg' property (%d)\n", nc, rc); return rc; } spi->chip_select = value; /* Device speed */ rc = of_property_read_u32(nc, "spi-max-frequency", &value); if (rc) { dev_err(&ctlr->dev, "%pOF has no valid 'spi-max-frequency' property (%d)\n", nc, rc); return rc; } spi->max_speed_hz = value; ```   spi_device结构体中的spi->chip_select的值,是spi设备树子节点的reg属性,即reg代表片选,这个地方在设备驱动生成设备的时候会用到。总而言之,会生成两个spi_device设备挂载在spi总线上。   后面的就都是一些收尾工作了。hisi_spi_transfer_one_message是发送和接收的函数,我看不下去了,里面的一些发送接收时序和具体的寄存器配置有兴趣自己看一下吧,但是应该只有厂家搞得懂。
  • [技术干货] 关于liteOS串口通信问题
    今天使用liteOS进行串口数据通信,主要是通过UART2连接外部设备进行数据通信发现以下问题,uart1在liteos中是默认就初始化的,且不能为之配置中断回调函数,亲测一向uart发送数据程序就会卡死,并且使用Transmit之类的函数,也不会打印数据,只能使用printf坑啊uart2串口就很uart1不同了,我一上午都在抓头发苦想uart2为啥接收不到数据,直到晚上,发现在初始化的时候liteos并没有初始化uart2坑啊uart2可以使用HAL库中的函数进行数据通信,不受影响我的发送数据的函数是这个,直接操纵寄存器即,当然也可以使用HAL_UART_Trainsmit来发送数据//串口发送一个字节static void MYUSART_SendData(uint8_t data){    while((USART2->ISR&0X40)==0);     USART2->TDR = data;}接收数据使用这个,没有写在中断函数里面HAL_UART_Receive(&huart2,(uint8_t *)USART2_RX_BUF,USART2_MAX_RECV_LEN,500);//接收数据我的开发方式比较特殊,发送数据之后立马读取数据,所以没写在中断,虽然我配置了中断,也写了中断回调函数但是我把回调函数注释了,发现还可以运行,奇怪的知识又增加了!!!if (huart->Instance == USART2)// {// // HAL_UART_Receive_IT(&huart2, USART2_RX_BUF, sizeof(USART2_RX_BUF)); //????????// }这是中断回调函数甚至,我不配置中断,也可以运行,真是离谱他妈给离谱开门。。。总结在liteOS中,尽量不要为串口添加中断中断配置文件里都有了,在代码里使能串口就了发送数据可正常使用HAL库或标准库接收数据如果不是太多或者不及时处理的,尽量不要用中断接收最后讲讲我的开发开发环境:iot studio +mdk v5+STM32L431RCT6小熊派开发板+AS608指纹识别模块+liteOS目前已成功修改as608驱动代码,实现了与指纹模块的握手通信,使用的是HAL库如果有类似我这种开发问题的,可以在帖子下留言,问题+需求+邮箱,源代码等我会通过邮箱发送也可以私信我邮箱Njthdsj@163.com,描述您的问题需求,我会尽可能帮助您
  • [交流吐槽] 聊聊万物互联-Wi-Fi6
    WiFi的历史从802.11的FHFS,DSSS到802.11b的DSSS,到802.11a的OFDM,802.11g的ERP(将OFDM从5G迁移到了2.4G),到802.11n的更宽频带(40MHz)的OFDM技术,到802.11ac的进一步拓宽(80, 80+80,160MHz)的OFDM技术,到802.11ax的更窄的子载波(78.125kHz)的OFDM技术,到讨论中的802.11be的320MHz的OFDM技术。OFDM技术从802.11a,大概2001年的时候,到现在,已经持续演进了20年了,它的抗多径效应,适合无线空间的复杂环境。因为802.11ax(Wi-FI 6)希望覆盖更广的空间,所以把载波宽度进一步变小了。每一代的发展,频带基本是越来越宽,似乎是可以无限的把频宽扩展下去。但是这应该是存在问题的,多样化的需求下,大频宽是可能浪费频段的。毕竟有些地方只需要小的频宽就好了。802.11ax还定义了一个仅支持20MHz的模式,也是瞄准了万物互联的趋势下,小数据,低能耗的搭配。但是802.11be(TB Wi-Fi 7)又把带宽变得更大了,每一代总是希望能更快的。但是单纯更快有什么意思呢。OFDM不止可以分频带,因为网络的特性,还可以分用户,这是Wi-Fi现在越来越看重的,希望越来越多的用户都是使用这个网络,而且还要能用。从802.11ac(2013)开始的Wi-Fi5已经引入了多用户的观念,对应的技术是MU-MIMO。它是这么样的,不同的用户用的是不同的天线。到了802.1ax(Wi-Fi6),支持上行的MU-MIMO,而且还把一个频段同时分配给了多个用户(OFDMA),每个用户最少可以只用2MHz的带宽。一下还把上行和下行都给加进去了。不止如此,还加入了BSS Coloring技术,弱化Exposed Node的问题,提升密集网络覆盖下的并行性。Wi-Fi6有这么多优势,其实它的主要着笔点在于密集用户,密集网络。想想现在Wi-Fi6的设备已经都出来1两年了,但是也并没有多改变生态。其实现在大家也觉得够用了,就像我自己,这种Wi-Fi6的理念对人这个用户来讲真的是有点超前了,哪里去找如此密集的人流和网络?办公场所,体育场?这些场合毕竟是少数。自动化生产车间也许是比较合适的,不过不是人流,而是物流。Wi-Fi7似乎要在这个道路上越走越远,越来越远…
  • [交流分享] 聊聊万物互联-Wi-Fi6
    WiFi的历史从802.11的FHFS,DSSS到802.11b的DSSS,到802.11a的OFDM,802.11g的ERP(将OFDM从5G迁移到了2.4G),到802.11n的更宽频带(40MHz)的OFDM技术,到802.11ac的进一步拓宽(80, 80+80,160MHz)的OFDM技术,到802.11ax的更窄的子载波(78.125kHz)的OFDM技术,到讨论中的802.11be的320MHz的OFDM技术。OFDM技术从802.11a,大概2001年的时候,到现在,已经持续演进了20年了,它的抗多径效应,适合无线空间的复杂环境。因为802.11ax(Wi-FI 6)希望覆盖更广的空间,所以把载波宽度进一步变小了。每一代的发展,频带基本是越来越宽,似乎是可以无限的把频宽扩展下去。但是这应该是存在问题的,多样化的需求下,大频宽是可能浪费频段的。毕竟有些地方只需要小的频宽就好了。802.11ax还定义了一个仅支持20MHz的模式,也是瞄准了万物互联的趋势下,小数据,低能耗的搭配。但是802.11be(TB Wi-Fi 7)又把带宽变得更大了,每一代总是希望能更快的。但是单纯更快有什么意思呢。OFDM不止可以分频带,因为网络的特性,还可以分用户,这是Wi-Fi现在越来越看重的,希望越来越多的用户都是使用这个网络,而且还要能用。从802.11ac(2013)开始的Wi-Fi5已经引入了多用户的观念,对应的技术是MU-MIMO。它是这么样的,不同的用户用的是不同的天线。到了802.1ax(Wi-Fi6),支持上行的MU-MIMO,而且还把一个频段同时分配给了多个用户(OFDMA),每个用户最少可以只用2MHz的带宽。一下还把上行和下行都给加进去了。不止如此,还加入了BSS Coloring技术,弱化Exposed Node的问题,提升密集网络覆盖下的并行性。Wi-Fi6有这么多优势,其实它的主要着笔点在于密集用户,密集网络。想想现在Wi-Fi6的设备已经都出来1两年了,但是也并没有多改变生态。其实现在大家也觉得够用了,就像我自己,这种Wi-Fi6的理念对人这个用户来讲真的是有点超前了,哪里去找如此密集的人流和网络?办公场所,体育场?这些场合毕竟是少数。自动化生产车间也许是比较合适的,不过不是人流,而是物流。Wi-Fi7似乎要在这个道路上越走越远,越来越远…
  • [技术干货] 【智慧园区设施云】一文看懂Modbus, RTU, RS485等名词的联系
    Modbus rtu和Modbus tcp两个协议的本质都是MODBUS协议,都是靠MODBUS寄存器地址来交换数据;但所用的硬件接口不一样,Modbus RTU一般采用串口RS232C或RS485/422,而Modbus TCP一般采用以太网口。现在市场上有很多协议转换器,可以轻松的将这些不同的协议相互转换 如:Intesisbox可以把modbus rtu转换成Modbus tcp实际上Modbus协议包括ASCII、RTU、TCP。标准的Modicon控制器使用RS232C实现串行的Modbus。Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式,数据通讯采用Maser/Slave方式。Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验.ModbusTCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。TCP和RTU协议非常类似,只要把RTU协议的两个字节的校验码去掉,然后在RTU协议的开始加上5个0和一个6并通过TCP/IP网络协议发送出去即可 Modbus协议定义的寄存器地址是5位十进制地址,即:线圈(DO)地址:00000~09999触点(DI)地址:10000~19999输入寄存器(AI)地址:30000~39999输出寄存器(AO)地址:40000~49999由于上述各类地址是唯一对应的,因此有些资料就以其第一个数字区分各类地址,即:0x代表线圈(DO)类地址,1x代表触点(DI)类地址、 3x代表输入寄存器(AI)类地址、4x代表输出寄存器(AO)类地址。在实际编程中,由于前缀的区分作用,所以只需说明后4位数,而且需转换为4位十六进制地址。简单点说,modbus有四种数据,DI、DO、AI、AODI: 数字输入,离散输入,一个地址一个数据位,用户只能读取它的状态,不能修改。比如面板上的按键、开关状态,电机的故障状态。DO: 数字输出,线圈输出,一个地址一个数据位,用户可以置位、复位,可以回读状态,比如继电器输出,电机的启停控制信号。AI: 模拟输入,输入寄存器,一个地址16位数据,用户只能读,不能修改,比如一个电压值的读数。AO: 模拟输出,保持寄存器,一个地址16位数据,用户可以写,也可以回读,比如一个控制变频器的电流值。无论这些东西被叫做什么名字,其内容不外乎这几种,输入的信号用户只能看不能改,输出的信号用户控制,并可以回读。离散的数据只有一位,模拟的数据有16位。
  • [技术干货] 单片机
    单片机(Single-Chip Microcomputer)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。从上世纪80年代,由当时的4位、8位单片机,发展到现在的300M的高速单片机
  • [行业资讯] 涨到300元的STMCU为何无法被国产替代?
    在芯片大缺货的时代背景下,涌现出了许多财富故事,让人羡慕不已。突然有一天,这个故事似乎找到了笔者头上。 有一个开工厂的朋友找到笔者,对我说:“你人脉广认识人多,能帮我找一颗料吗?” 笔者以为时来运转要发财了,于是帮忙了解一下,这颗料STM32F303VET6已经停产了,不含税售价300元/颗。当然,笔者这种半路出家的去找IC,最终也没有成功达成交易。 不过笔者大概对目前的MCU市场行情有了一定的概念,一颗用到扫地机器人上的,消费级/工业级的MCU。如果是用ST的可以卖到300元,但是参数类似,管脚兼容的国产MCU,就只能卖10元上下了。 为何参数性能相差不大的芯片,价格相差如此之大?这么贵的芯片都是谁在买,又是谁在卖,最终用到什么地方去了?带着这些疑问,笔者找到了在电子业摸爬滚打超过20年,现在自己做方案公司的“骨灰级”工程师兼老板老张(化名),希望他能帮我答疑解惑。  号称全兼容的国产MCU为何无法替代ST? 事实上,国产MCU之所以跟国外品牌的MCU有如此大的价差,首先一个很重要的原因是很难替代,尽管很多国产MCU都宣传可以实现全兼容。 “市场上基本没有真正可以实现全兼容的,听说极海的MCU可以实现全兼容,但我没试过。”老张表示,几乎所有的国产MCU买回来都是无法直接实现替代的,必须要改源代码,或者调软件算法。 “以前台湾的义隆和松瀚就是这么成功的,厂商提供一个编好的转换文件,客户拿过来烧录直接就能用。”老张从使用者的角度来分析,客户最需要的是能够方便使用,最好是一点不要改,直接烧录软件就可以跑。对于很多方案或终端客户来说,采购芯片的目的是买回来就可以直接用。类似于改软件调试,或改源代码的工作,很多的客户是做不了的,这种情况下他们只能选择买ST或其它国际品牌的MCU,因为工作量最小。“深圳人员流动性这么大,前年这个人写的软件,今年可能这个人就不在了。换个人来开发软件时间上可能会很久。” 除了软件方面国产难兼容,另一个难替代的问题是少数厂商在硬件方面虚报参数。“一般数字部分问题不大,主要是模拟的部分。”老张表示,国产的MCU很多自己没有模拟设计的能力,要么是采购其它家的模拟器件进行集成,要么是买IP,要么是抄的,有时候标的参数在实际使用中会出现问题。 相对来说,ST的MCU基本上标的是多少参数,实际使用中就一定是多少参数,不会有偏差,这让开发者省了很多功夫。“你看一下ST或者Microchip的单片机规格书,再对比一下国产的规格书,就会明白了。”老张表示,ST的规格书会用很多页详细的介绍芯片的各种模拟部分的规格,相对来说国产的单片机信息就很少了,类似于ADC的输入阻抗这些都没有,只能客户自己去实测。“如果你测的是低阻的东西问题不大,如果测高阻的东西,比如做额温枪这种,这个参数就很重要了。” 老张也表示,目前国内一些比较大牌的MCU,比如兆易创新,测试下来参数都可以。有虚标情况的主要是一些杂牌的比较低端的8位MCU。总结起来,国产MCU难以实现替代主要有三点:1.软件兼容性没做好;2.模拟部分的性能参数有水分;3.规格书不够详细。 其实老张自己也搞不懂ST的生态是怎么建立起来,而且做得这么强大的。据他所说,20年前开始研究单片机的时候,那个时候ATMEL才是单片机之王。ST应该是随着ARM生态的成熟逐渐成长起来的。“当时做8位机的大多数采用51指令兼容,非51阵容的就只有摩托罗拉、Microchip,还有一个日立。”  什么人在买卖国产MCU? “反正我不买紧缺的进口芯片,我一般都想办法避开。”老张表示,之所以不用进口芯片,因为他的公司主要开发消费类产品,而消费领域由于成本考虑不允许使用太贵的芯片。“我必须找到一颗最适合我用的MCU,而不是选择通用的MCU。” 要避开紧缺的物料,需要自身有一定的技术研发能力,同时要对市场行情比较了解。比如虽然MCU涨价厉害,但是用到IOT领域的蓝牙SOC涨价并不厉害,这些蓝牙SOC本身就自带通用的MCU。有一些研发能力强的客户就会买回来降级使用。“比如有些人就用中科蓝汛的SOC去做筋膜枪了,杰理的一些做TWS的蓝牙也可以做通用MCU用。”老张表示,蓝牙SOC也涨价了,但是相比其它领域涨价幅度并不高,客户普遍可以承受。 除了ST、Microchip这些国外芯片价格疯涨,很多国产MCU也跟着涨价。“很多人不明白这些国产MCU最开始卖多少钱,最早也就两三块钱涨到七八十一颗,好多炒货的就跟着囤货,后来发现有人10多块抛货,他们就只好跳楼了。”老张表示,他作为买家,其实很不理解这些买高价芯片的人。比如一颗MCU炒到200元,买2万颗就是400万元。“我怀疑你做一单产品能不能卖400万元?既然卖芯片这么赚钱,那还做什么产品?这个芯片变成金融产品了。” 那么这些高价货从哪来的呢?尽管原厂在管控,但是仍然不断有货源出现,其中一个来源是代理商与大的终端客户。因为这些大的终端客户都是以平价或者仅仅涨一点的价格拿货的,主要看这些客户跟原厂的关系好坏。“很多厂都是进100颗,拿50颗做产品,自己囤50颗。”老张表示,随着芯片的不断看涨,最后这些工厂手里的芯片变成了零成本,拿着就不断升值,卖出去就有钱赚。 还有一个来源就是二手的翻新货/散新货。“我的一个朋友用NXP,可能原本卖7~8元的料,他用9元买来二手货,然后13元卖出去。市场上新货炒到了7~80元。”老张表示,很多二手翻新货处理得很好,客户很难看出来。有些甚至比正品新货的价格还贵。 让老张想不明白的是,现在出口需求这么低迷,很多工厂已经停产或者只开一半工,这些大厂囤这么多货最后会不会砸手里?他认为最终市场会做出反应,这些高价货最后会跌破原本的价格。“以往都是这样,比如无线充火的时候,大家炒电容,4毛钱的电容炒到2元,最后跌到2毛3在街上卖。” 老张认为,现在热钱投资这么多,芯片公司如雨后春笋般涌现。但是另一方面芯片公司的挣钱能力越来越差,投入一个亿可能只能挣100万。“一颗芯片挣1分钱,做成产品可能挣1元钱。”老张表示,这种情况下国产芯片还是扎堆做低端市场打价格战,高端比如车规芯片又做不了。扎堆低端市场又碰到上游晶圆产能紧缺,好多小原厂又拿不到产能。他认为,国产MCU的泡沫化现状被产能紧缺掩盖了,一旦产能缓解降价,很多客户还是会回头来用国际品牌的MCU。 那么产能问题什么时候能缓解?在日前举行的RT-Thread开发者大会上,瑞萨MCU中国区市场部总监沈清认为,2022年产能仍然会紧缺。在缺货的情况下,国产MCU会比国际品牌更缺货。至于车规级的MCU可能缺货时间会更久,国民技术方案开发部执行总监赵永刚就认为,至少要两到三年产能才有可能缓解。  总结:国产MCU还有哪些功课需要补齐? 跟市场传闻不同,极海半导体的市场产品经理陈成表示,极海并不是全兼容ST,而是相比其它国产的竞争对手可能要做60%的改动,极海只需要做30%的改动。他同时也承认“在模拟这块,我们和ST还是有一些差距的。” 专门为NXP和英飞凌提供第三方服务以及MCU推广的逐飞科技总经理范兵,则对笔者表示。目前的MCU厂商能够获得多大的支持,话语权有多大,完全取决于ARM的支持。所以越来越多的国产厂商开始考虑RISC-V的架构。但是做RISC-V的生态不够完善,能找到的资源很少。逐飞科技一直想帮助国产MCU来做行业规范和标准化,但是在目前国产MCU刚刚开始爆发,野蛮生长的时候,想做规范谈何容易。“我们的工程师需要形成自己的开发习惯”范兵介绍,目前逐飞科技也在积极协助国产MCU厂商丰富自己的文档资料,希望能构建起完善的生态。不过笔者认为,在目前百花齐放的MCU市场,想要构建统一的生态和标准,仍然为时尚早。 作为国产MCU的代表企业之一,华大半导体市场经理张建文认为,与国际MCU巨头厂商相比,国产MCU的差距主要体现在三个方面:1.很多高端芯片的IP是国际巨头自研的,国产MCU如果要做高端的产品,很多买不到IP,只能自研;2.芯片厂商的系统能力,解决方案的能力要提升;3.国产MCU的标准化做得比较差。他表示,欧美的MCU厂商可能有好几十个团队做标准化,但是国产MCU没有这样的团队。“有些该走的路你是跳不过去,总有一天的债是需要还的。” 笔者认为,当潮水褪去,才能发现哪些人在裸泳。对于国产MCU来说,大缺货是机遇,也是陷阱。如果国产MCU不趁此缺货的契机苦练内功,补齐生态短板,终有一天客户仍然会回到欧美品牌的怀抱。
  • [技术干货] IoT干货整理汇总帖
    无线Mesh网络WMNhttps://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=171084IoTStudio&IoTStage;学习笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=146566简易加法计算器https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=141595宏内核和微内核https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=141264数码管的动态显示https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=135343LED 闪烁程序https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=134011蓝屏代码大全和基本解救方法整理笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=133467晶振&复位电路https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=133325LiteOS任务管理应用实践笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=131814数码管https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=131094物联网操作系统Huawei LiteOShttps://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=130799逻辑电路与逻辑运算笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=130782三极管在数字电路中的应用https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=130610去耦电容的应用https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=130601物联网操作系统笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=130290华为云IoT设备侧开发笔记https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=128998【IoT】0基础玩转华为IoT学习分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=128645华为云IoT应用侧开发https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=128010华为云IoT设备编解码插件https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=12722151单片机点阵 LED小实践https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=124345同时同频全双工技术https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=124441物联网平台二次开发https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=124131IoT边缘计算服务https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=123895物联网中几个常用协议总结https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=121943华为智慧家庭解决方案笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=1206105G 三大场景应用https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=1200205G 的关键技术——三大关键革新https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=1161095G 标准演进与产业发展https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=114054NB-IoT 的优点总结https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=112885NB-IoT 标准演进与产业发展笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=112153为什么说单片机是最小系统https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=111475无线通信技术对比https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=111125LPWA 通信技术笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=110621蜂窝移动通信技术笔记分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=109875几个常见的短距无线通信技术分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=109870利用51单片机写一个流水灯程序分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=109509物联网SIM卡和手机SIM卡真的是一回事吗?https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=109503什么是NB-IoT?https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=109493Histreaming手机APP操作HiSpark—WIFI IOT分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=107829HiSpark开发板--CH340G&CP2102;&FT230x;驱动安装分享https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=107412何谓管程?管程组成?管程的必要性?https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=144602路由与交换之VRRP协议特性与配置小实践https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=143366为什么说直到出现中断和通道技术后多道程序概念才变得有用?https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=142122指令集与单片机的小故事https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=141641鸿蒙智联设备认证https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=169940海思Wi-Fi后装全屋智能解决方案六大优势一目了然https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=161531HarmonyOS的前世今生https://bbs.huaweicloud.com/forum/forum.php?mod=viewthread&tid=146829    华为云IoT开发者社区    提供 IoT开发所需要的开发者平台、API/SDK参考、开发指南、代码样例、开发工具、视频课程等各类资源助力开发者快速完成物联网产品与解决方案的开发【活动合集】>>  IoT 社区活动实时更新,参与0门槛,回帖就有奖,来赢万元大奖!【干货合集】>>  从入门、实战到落地,华为“端边云”IoT全栈开发实战指南!【资源汇总】>>  IoT 开发者社区,专家直播、免费资源、课程、实验、认证,你要的通通有!【交流求助】>>  IoT 论坛,问题求助、交流吐槽、分享学习,专家在线等你!华为云IoT,从联接使能、数据使能、生态使能三个维度,提供端边云一体协同的全场景物联网云服务,持续构建万物互联的智能世界。联接使能:通过LiteOS、IoT边缘、设备接入管理、全球SIM联接等服务,覆盖端侧开发、边云接入、流量管理等环节,使能万物极简接入,上电即上云;数据使能:IoT数据分析服务,以高性能的孪生建模能力构建IoT数字孪生,进一步释放IoT数据潜力;生态使能:IoT Stage,以标准物模型为核心,面向伙伴提供一站式的体验、设计、集成平台,加速伙伴商业变现。华为云IoT聚焦物联网基础设施建设,联合伙伴,共筑产业繁荣,以联合创新的行业物联网解决方案,加速千行百业的智能升级。
  • [交流分享] 聊聊万物互联-Wi-Fi6
    WiFi的历史从802.11的FHFS,DSSS到802.11b的DSSS,到802.11a的OFDM,802.11g的ERP(将OFDM从5G迁移到了2.4G),到802.11n的更宽频带(40MHz)的OFDM技术,到802.11ac的进一步拓宽(80, 80+80,160MHz)的OFDM技术,到802.11ax的更窄的子载波(78.125kHz)的OFDM技术,到讨论中的802.11be的320MHz的OFDM技术。OFDM技术从802.11a,大概2001年的时候,到现在,已经持续演进了20年了,它的抗多径效应,适合无线空间的复杂环境。因为802.11ax(Wi-FI 6)希望覆盖更广的空间,所以把载波宽度进一步变小了。每一代的发展,频带基本是越来越宽,似乎是可以无限的把频宽扩展下去。但是这应该是存在问题的,多样化的需求下,大频宽是可能浪费频段的。毕竟有些地方只需要小的频宽就好了。802.11ax还定义了一个仅支持20MHz的模式,也是瞄准了万物互联的趋势下,小数据,低能耗的搭配。但是802.11be(TB Wi-Fi 7)又把带宽变得更大了,每一代总是希望能更快的。但是单纯更快有什么意思呢。OFDM不止可以分频带,因为网络的特性,还可以分用户,这是Wi-Fi现在越来越看重的,希望越来越多的用户都是使用这个网络,而且还要能用。从802.11ac(2013)开始的Wi-Fi5已经引入了多用户的观念,对应的技术是MU-MIMO。它是这么样的,不同的用户用的是不同的天线。到了802.1ax(Wi-Fi6),支持上行的MU-MIMO,而且还把一个频段同时分配给了多个用户(OFDMA),每个用户最少可以只用2MHz的带宽。一下还把上行和下行都给加进去了。不止如此,还加入了BSS Coloring技术,弱化Exposed Node的问题,提升密集网络覆盖下的并行性。Wi-Fi6有这么多优势,其实它的主要着笔点在于密集用户,密集网络。想想现在Wi-Fi6的设备已经都出来1两年了,但是也并没有多改变生态。其实现在大家也觉得够用了,就像我自己,这种Wi-Fi6的理念对人这个用户来讲真的是有点超前了,哪里去找如此密集的人流和网络?办公场所,体育场?这些场合毕竟是少数。自动化生产车间也许是比较合适的,不过不是人流,而是物流。Wi-Fi7似乎要在这个道路上越走越远,越来越远…
  • [问题求助] 【XXX产品】【XXX功能】一句话描述问题
    【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [交流分享] 回头看var、let和const
    varvar声明的作用域是函数作用域,如果一个变量在全局用var进行了声明,那么这个变量就会作为window对象的一个属性,可以通过window.XXX来访问。如果在函数体内部声明了这个变量,则这个变量会在函数执行完后被销毁。var声明会被提升,这里要注意,被提升的是声明,而不是赋值var a = 100;这一行代码其实可以被分解成为两个部分:var a;a = 100;被提升的是var a;这一句,赋值这一句只有在代码执行到时才会执行,看个例子:function foo(){    console.log(age);    var age = 21;}foo()//输出的结果是undefined这段代码等同于:function foo(){    var age;//变量age被提升了    console.log(age);    age = 21;}foo()//输出的结果是undefined执行到console.log(age)的时候age只被声明了,但是还没被赋值,所以输出来了undefined。同时var支持多次声明同一个变量,不会报错,后面的赋值会替代前面的赋值。var a;var a = 10;var a = 20;console.log(a) //20而且a这个变量的内存地址始终是相同的,这可能会发生一些奇怪的现象,我们留到后面说。letlet相对于前面的var有一个很大的区别就是,var是函数作用域,而let是块级作用域,什么是块呢?我的理解是块就是由一对{}包括起来的代码,不严谨地说函数作用域其实也算是一种块级作用域,因为函数体地代码也是由一对{}包裹起来的。换句话说let的作用域受限的地方会比var多,如何理解这句话呢,看代码:{    let a = 10;}console.log(a);//Uncaught ReferenceError: a is not definedif (true) {     let age = 26;     console.log(age); // 26} console.log(age);// ReferenceError: age 没有定义作为对比,var是这样的if (true) {     var age = 26;     console.log(age); // 26} console.log(age); // 26console.log(window.age) //26这就是一个块级作用域例子,在块里面声明的变量在块的外部是不能访问的,出了块就会被销毁。与var不同的还有let不允许在一个块里面重复声明 一个相同变量let a;let a;//会报错var a;let a;//会报错对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量, 它们只是指出变量在相关作用域如何存在。前面说了var的变量声明是会被提升的,但是let就不会被提升,会出现暂时性死区function foo(){    console.log(age);    let age = 21;}foo()// ReferenceError:age 没有定义如果在浏览器的环境下,用let定义一个全局变量,这个全部变量在全局任何地方都是能被访问的,但是与var不同的是,let不会让这个变量window对象的一个属性。在for中使用let的好处:先看用var 的坏处:for (var i = 0; i < 5; ++i) {     // 循环逻辑}console.log(i); // 5由于for循环不是函数作用域,是块级作用域,所以var声明的变量在for循环结束后会“泄露”,这并不是什么好事,为了避免这种情况,可以用let。for (let i = 0; i < 5; ++i) {     // 循环逻辑}console.log(i); // ReferenceError: i 没有定义其实仔细的同学已经发现了,在for里面用var进行声明a,算是多次重复声明a,这些a的内存地址都是一样的,而在for里面用let进行声明,则会在5个块里面各自声明一个a,互不影响,这些a的内存地址都是不一样的,知道了这一点,我们再来看个有意思的for (var i = 0; i < 5; ++i) {     setTimeout(() => console.log(i), 0)}// 你可能以为会输出 0、1、2、3、4// 实际上会输出 5、5、5、5、5for (let i = 0; i < 5; ++i) {     setTimeout(() => console.log(i), 0)}// 会输出 0、1、2、3、4为什么会造成这种情况?这其实需要知道一点有关于宏任务、微任务的相关知识,再结合本次讲的内容,就可以测底搞明白了。不懂的可以去翻翻我之前的博客,看不懂的可以在线问我,在能力范围内必定解答。首先setTimeOut的回调函数是一个宏任务,会在当前的宏任务,以及微任务执行完后才会执行,这里没有微任务,所以不用考虑微任务是什么。这里的宏任务就是这个for循环,这个for循环执行完后(往宏任务队列push了5个宏任务)才会按顺序执行这些setTimeOut的回调函数“() => console.log(i)”,当for里用var的时候,i在循环结束的时候等于5,而且i不会被销毁,后面开始执行5个回调函数的时候,输出来的自然是5个5,而在for里用let的时候,每个回调函数会引用不同的i,这些i在前面也说到了,是有不同的内存地址的,所以称他们为“不同的i”,输出来的就会是0,1,2,3,4了。这里有人会问了:let声明的变量除了块级作用域不就被销毁了么,这里的是for结束后再开始执行回调的,以及出了块,怎么还可以访问i的值?这要细说的话就要说到闭包和作用域链了,下次会单独写一篇来说这个。来看看红宝书的话而在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。 每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循 环执行过程中每个迭代变量的值。const这里把const放到最后,因为const和let很像,唯一的不同就是let声明的变量可以修改值,而const不可以,所以const的声明和赋值必须同时完成const a = 10;//只能这样赋值当然如果const定义的变量赋值为一个对象,修改这个对象内部的属性和方法是允许的。const 声明的限制只适用于它指向的变量的引用。换句话说,如果 const 变量引用的是一个对象, 那么修改这个对象内部的属性并不违反 const 的限制。
  • [问题求助] 为啥单片机向电脑发送的字符串是乱码
    两台单片机分别向电脑发送含中文字符串能正常显示,电脑向单片机发送含中文字符串再返回回来也正常显示,但是一台单片机向另一台单片机发送含中文字符串再由接收的单片机向电脑发送,结果却只有中文显示乱码;如下图绿色的是发送的字符串,黑色的是接收到的字符串,有什么解决办法吗? 572221572222
  • [其他] TensorFlow-py27通用模板
    简介搭载TensorFlow1.8 AI引擎,运行环境为“python2.7”,内置输入输出模式为未定义模式,请根据模型功能或业务场景重新选择合适的输入输出模式。使用该模板导入模型时请选择到包含模型文件的model目录。模板输入存储在OBS上的TensorFlow模型包,确保您使用的OBS目录与ModelArts在同一区域。对应的输入输出模式未定义模式,可覆盖,即创建模型时支持选择其他输入输出模式。模型包规范模型包必须存储在OBS中,且必须以“model”命名。“model”文件夹下面放置模型文件、模型推理代码。模型推理代码文件不是必选文件,如果有,其文件名必须为“customize_service.py”,“model”文件夹下有且只能有1个推理代码文件使用模板导入的模型包结构如下所示:model/ │ ├── 模型文件 //必选,不同的框架,其模型文件格式不同,详细可参考模型包示例。 ├── 自定义Python包 //可选,用户自有的Python包,在模型推理代码中可以直接引用。 ├── customize_service.py //可选,模型推理代码,文件名称必须为“customize_service.py”,否则不视为推理代码。模型包示例TensorFlow模型包结构发布该模型时只需要指定到“model”目录。OBS桶/目录名 |── model 必选,文件夹名称必须为“model”,用于放置模型相关文件。 ├── <<自定义python包>> 可选,用户自有的Python包,在模型推理代码中可以直接引用。 ├── saved_model.pb 必选,protocol buffer格式文件,包含该模型的图描述。 ├── variables 对“*.pb”模型主文件而言必选。文件夹名称必须为“variables”,包含模型的权重偏差等信息。 ├── variables.index 必选 ├── variables.data-00000-of-00001 必选 ├──customize_service.py 可选,模型推理代码,文件名称必须为“customize_service.py”, 有且只有1个推理代码文件。“customize_service.py”依赖的“py”文件可以直接放“model”目录下。