首页 > 其他分享 >nRF Connect SDK Basic

nRF Connect SDK Basic

时间:2024-12-26 14:31:32浏览次数:5  
标签:msgq thread spi uart nRF UART user Basic SDK

用户在使用 nRF connect SDK(NCS) 的时候经常会操作的外设有GPIO,I2C,SPI,UART。我们就以 NCS 2.7.0 中的例程代码 nrf\samples\bluetooth\peripheral_lbs 为基础,来演示上述外设的简单使用。使用的硬件是开发板 nRF52840 DK. 

准备工作

  • 首先我们在原本的工程目录的 boards 文件夹里,添加文件 nrf52840dk_nrf52840.overlay。通过这个文件我们可以修改 devicetree。 编译完成后,我们可以查看 build\zephyr\zephyr.dts,以确认devicetree 的更改是否生效。
  • 我们还可以通过修改 prj.conf 来修改 Kconfig。编译完成后,我们可以查看 build\zephyr\.config 以确认 Kconfig 的更改是否生效。

 

GPIO 控制

  • 首先我们演示如何删除原有的按键和LED的 node 。按照下面的代码,来修改 devicetree,就可以删除 button3 和 led3。
     / {
        aliases {
            /delete-property/ sw3;
    		/delete-property/ led3;
        };
    };
    
    
    /delete-node/ &button3;
    /delete-node/ &led3;
    

      

  • 接着我们来更改控制 led2 的管脚。这里我们用 P0.04 控制 led2。
    &led2 {
    	gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
    };
    

     

  • 最后我们添加一个用户 GPIO 。这里添加了一个名为 user_gpios 的 node。然后又定义了 user_io0,它是 user_gpios 的 subnode。            
    / {
    	user_gpios {
    		compatible = "gpio-leds";
    		user_io0: user_io0 {
    			gpios = <&gpio0 16 GPIO_ACTIVE_LOW>;
    			label = "user gpio 0";
    		};
    	};
    };
    

      

    我们不仅在 devicetree 里添加这个 GPIO ,还要在 main.c 里添加代码使用这个GPIO。下面这句代码中,我们声明了结构体变量  user_gpio0,并用宏 GPIO_DT_SPEC_GET 根据 devicetree 里的定义初始化它。
    const struct gpio_dt_spec user_gpio_0 = GPIO_DT_SPEC_GET(DT_NODELABEL(user_io0),gpios);
    

      

    下面这段代码中  gpio_is_ready_dt 是用来检查 GPIO 的状态是否是就绪。用函数 gpio_pin_configure_dt 把 user_gpio_0 配置成输出。gpio_pin_toggle_dt 用来翻转 GPIO。
    	if (!gpio_is_ready_dt(&user_gpio_0)) {
    		printk("%s: device not ready.\n", user_gpio_0.port->name);
    		return 0;
    	}
    	gpio_pin_configure_dt(&user_gpio_0, GPIO_OUTPUT_ACTIVE);
    
    	for (index = 0; index < 100; index++) {
    		gpio_pin_toggle_dt(&user_gpio_0);
    		k_sleep(K_MSEC(100));
    	}
    

      

    从下面的代码可以看出翻转 GPIO 这个操作有两种 API 可以调用。二者的主要区别是 gpio_pin_toggle_dt 不需要指明引脚 。
    /**
     * @brief Toggle pin level from a @p gpio_dt_spec.
     *
     * This is equivalent to:
     *
     *     gpio_pin_toggle(spec->port, spec->pin);
     *
     * @param spec GPIO specification from devicetree
     * @return a value from gpio_pin_toggle()
     */
    static inline int gpio_pin_toggle_dt(const struct gpio_dt_spec *spec)
    {
    	return gpio_pin_toggle(spec->port, spec->pin);
    }
    

      

I2C 设备控制

Nordic 的芯片中 I2C 接口是由外设 TWI 来实现的,I2C master 由 TWIM 实现, I2C master 由 TWIS 实现。这里将演示如何用一个 TWIM 来连接两个 I2C slave 设备。

  • 首先我们还是先修改 devicetree。我们使用 i2c1 这个 node。 一方面按照应用的要求修改这个 node 的 propertise,另一方面在这个 node 里创建两个 sub-node。
    1. i2c 的时钟频率通过 clock-frequency 来定义。
    2. i2c 的引脚通过 pinctrl-0 和 pinctrl-1 定义。我们将在后面分析 i2c1_default 和 i2c1_sleep 的定义。
    3. 这两个 sub-node 一个是 user_i2c_sensor,另一个是 user_i2c_eeprom。这两个 sub-node 通过 propertise reg 来定义各自的 I2C 地址。  
      &i2c1 {
      	status = "ok";
      	clock-frequency = <I2C_BITRATE_STANDARD>;
          pinctrl-0 = < &i2c1_default >;
          pinctrl-1 = < &i2c1_sleep >;
          pinctrl-names = "default", "sleep";
      	user_i2c_sensor: user_i2c_sensor@0 {
      		compatible = "i2c-user-define";
      		reg = <0xA>;
      	};
      	user_i2c_eeprom: user_i2c_eeprom@0 {
      		compatible = "i2c-user-define";
      		reg = <0x5>;
      	};       
      };        
      

         

    4. i2c1_default 和 i2c1_sleep的定义如下。TWIM_SDA 信号使用的是引脚 P0.04,TWIM_SCL 信号使用的是引脚 P0.03。          
      &pinctrl {
      	i2c1_default: i2c1_default {
      		group1 {
      			psels = <NRF_PSEL(TWIM_SDA, 0, 4)>,
      				<NRF_PSEL(TWIM_SCL, 0, 3)>;
      		};
      	};
      
      	i2c1_sleep: i2c1_sleep {
      		group1 {
      			psels = <NRF_PSEL(TWIM_SDA, 0, 4)>,
      				<NRF_PSEL(TWIM_SCL, 0, 3)>;
      			low-power-enable;
      		};
      	};
      
      };
      

 

  • 修改 prj.conf 添加 CONFIG_I2C=y
  • 修改完 devicetree 我们在来添加操作 i2c 的代码。分别定义 i2c1_sensor 和 i2c1_eeprom,它们对应刚才 i2c1 的两个子节点。
    const struct i2c_dt_spec i2c1_sensor = I2C_DT_SPEC_GET(DT_NODELABEL(user_i2c_sensor));
    const struct i2c_dt_spec i2c1_eeprom= I2C_DT_SPEC_GET(DT_NODELABEL(user_i2c_eeprom));
    
    i2c 设备在读写操作前无需调用 API 来配置 ,直接调用下面的写函数。
    	err = i2c_write_dt(&i2c1_sensor, buf, 1);
    	
    	err = i2c_write_dt(&i2c1_eeprom, buf, 1);
    
    通过逻辑分析仪我们可以看到如下的总线数据,操作的目标地址分别是我们在 devicetree 里设置的数值 0x05 和 0x0A 。

 

SPI 设备控制

Nordic 的芯片中 SPI 接口的 master 端通过 SPIM 实现, slave 端通过 SPIS 实现。这里将演示如何用一个 SPIM 来连接两个 SPI slave 设备。

  • 首先修改 devicetree。
    1. 这里我们使用 spi2, 并且关闭 spi1。在 nordic 的nRF52 系列芯片中,相同数字编号的 TWIM, TWIS, SPIM, SPIS 是共用一组硬件模块的。在上面 i2c 中我们已经使用 i2c1, 所以这里我们就不能同时使用 spi1了。
    2. cs-gpios 定义了 P0.26 和 P0.27 两 个CS 信号。 SPI 用不同的片选信号,区分不同的 slave 设备。
    3. devicetree node spi2 下定义了两个 sub-node 分别是 user_spi_adc 和 user_spi_flash。 sub-node 里定义了三个 propertise。propertise compatible 的取值来自于我们在工程里新添加的文件 dts\bindings\spi-user-define.yaml。 propertise reg 的取值和前面的 propertise cs-gpios 呼应,reg = <0> 的 sub-node 使用 cs-gpios 里面定义的第一个 CS 引脚。reg = <1> 的 sub-node 使用 cs-gpios 里面定义的第二个 CS 引脚。propertise spi-max-frequency 定义 SPI 的时钟频率。两个不同的 SPI 设备可以使用不同的时钟频率驱动。                                                            
      &spi1 {
      	status = "disabled";
      };	
      &spi2 {
      	status = "okay";
          cs-gpios = <&gpio0 26 GPIO_ACTIVE_LOW>,
                     <&gpio0 27 GPIO_ACTIVE_LOW>;
          pinctrl-0 = < &spi2_default >;
          pinctrl-1 = < &spi2_sleep >;
          pinctrl-names = "default", "sleep";
      
      	user_spi_adc: user_spi_adc@0 {
      		compatible = "spi-user-define";
      		reg = <0>;
      		spi-max-frequency = <DT_FREQ_M(8)>;
      	}; 
      	user_spi_flash: user_spi_flash@0 {
      		compatible = "spi-user-define";
      		reg = <1>;
      		spi-max-frequency = <DT_FREQ_M(8)>;
      	};        
      };       
      

                                                                                                                                                                                                                                                                                                             

    4.  来看一下我们新添加的 dts\bindings\spi-user-define.yaml 里面的内容。如下图 spi-user-define.yaml 里面包含了 spi-device.yaml 文件,这个文件的位置在目录 zephyr\dts\bindings\spi 。
      compatible: "spi-user-define"
      
      include: [spi-device.yaml]
      

        

      spi-device.yaml 文件里面定义了 spi 节点需要的一些 propertise。   比如我们在 sub-node 里定义的 propertise spi-max-frequency。 
      # Copyright (c) 2018, I-SENSE group of ICCS
      # SPDX-License-Identifier: Apache-2.0
      
      # Common fields for SPI devices
      
      include: [base.yaml, power.yaml]
      
      on-bus: spi
      
      properties:
        reg:
          required: true
        spi-max-frequency:
          type: int
          required: true
          description: Maximum clock frequency of device's SPI interface in Hz
        duplex:
          type: int
          default: 0
          description: |
            Duplex mode, full or half. By default it's always full duplex thus 0
            as this is, by far, the most common mode.
            Use the macros not the actual enum value, here is the concordance
            list (see dt-bindings/spi/spi.h)
              0    SPI_FULL_DUPLEX
              2048 SPI_HALF_DUPLEX
          enum:
            - 0
            - 2048
        frame-format:
          type: int
          default: 0
          description: |
            Motorola or TI frame format. By default it's always Motorola's,
            thus 0 as this is, by far, the most common format.
            Use the macros not the actual enum value, here is the concordance
            list (see dt-bindings/spi/spi.h)
              0     SPI_FRAME_FORMAT_MOTOROLA
              32768 SPI_FRAME_FORMAT_TI
          enum:
            - 0
            - 32768
        spi-cpol:
      

       

    5. SPI 引脚定义如下 CLK P0.28, MISO P0.29, MOSI P0.30。
          spi2_default: spi2_default {
              group1 {
                  psels = <NRF_PSEL(SPIM_SCK, 0, 28)>,
                          <NRF_PSEL(SPIM_MISO, 0, 29)>,
                          <NRF_PSEL(SPIM_MOSI, 0, 30)>;
              };
          };
          spi2_sleep: spi2_sleep {
              group1 {
                  psels = <NRF_PSEL(SPIM_SCK, 0, 28)>,
                          <NRF_PSEL(SPIM_MISO, 0, 29)>,
                          <NRF_PSEL(SPIM_MOSI, 0, 30)>;
                  low-power-enable;
              };
          };  
      

        

  • 修改 prj.conf 添加 CONFIG_SPI=y   CONFIG_SPI_ASYNC=y。
  • 在 main.c 里添加 SPI 的应用代码。下面这段代码定义了两个结构体变量,并通过宏 SPI_DT_SPEC_GET 用 devicetree 里的参数初始化了这两个结构体变量。
    #define SPI_OP     SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA \
                       | SPI_WORD_SET(8) | SPI_LINES_SINGLE
    static struct spi_dt_spec spim2_adc = SPI_DT_SPEC_GET(DT_NODELABEL(user_spi_adc), SPI_OP, 0);
    static struct spi_dt_spec spim2_flash = SPI_DT_SPEC_GET(DT_NODELABEL(user_spi_flash), SPI_OP, 0);
    

      

    spi 驱动支持多 buffer 所以要定义 buffer 个数,和每个 buffer 的长度。同样 spi 在读写之前无需调用配置函数,直接调用读写函数就行  
    	struct spi_buf_set tx_bufs;
    	struct spi_buf spi_tx_buf;
    
    	tx_bufs.buffers = &spi_tx_buf;
    	tx_bufs.count = 1;
    	spi_tx_buf.buf = buf;
    	spi_tx_buf.len = 2;
    
    	err = spi_write_dt(&spim2_adc, &tx_bufs);
    	err = spi_write_dt(&spim2_flash, &tx_bufs);
    

 

   下面是SPI的波形。可以看到和不同的 spi slave 设备通讯的时候, spi master 会拉低不同的 CS 引脚。            

  

UART 控制

Nordic 的芯片中 UART 接口叫做 UARTE。这里的 E 是指 EasyDMA , UART 可以使用 DMA 来连续收发。

  • 修改 Devicetree。这里使用 uart1。propertise current-speed 设置 uart 的波特率。 
    &uart1 {
    	status = "okay";
    	current-speed = <115200>;
    	pinctrl-0 = < &uart1_default >;
    	pinctrl-1 = < &uart1_sleep >;
    	pinctrl-names = "default", "sleep";
    };
    

      

    TXD pin 为 P1.02, RXD pin 为 P1.01。                                                                                       
    	uart1_default: uart1_default {
    		group1 {
    			psels = <NRF_PSEL(UART_RX, 1, 1)>;
    			bias-pull-up;
    		};
    		group2 {
    			psels = <NRF_PSEL(UART_TX, 1, 2)>;
    		};
    	};
    
    	uart1_sleep: uart1_sleep {
    		group1 {
    			psels = <NRF_PSEL(UART_RX, 1, 1)>,
    				<NRF_PSEL(UART_TX, 1, 2)>;
    			low-power-enable;
    		};
    	};
    

 

  • 修改 prj.conf 在里面添加 CONFIG_UART_ASYNC_API=y    CONFIG_UART_ASYNC_RX_HELPER=y。
  • 修改 main.c 添加 uart 收发代码。 uart_callback_set 设置 callback 函数 uart_cb。因为这里采用的是异步收发的模式,所以设置callback 函数是必备的。uart_rx_enable 使能接收。uart_tx 发送数据。
    	err = uart_callback_set(uart1, uart_cb, NULL);
    	//printk("uart_callback_set return %d\n", err);
    
    	err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS);
    	//printk("uart_rx_enable return %d\n", err);
    
    	err = uart_tx(uart1, uart_tx_buf, 6, SYS_FOREVER_MS);
    	//printk("uart_tx return %d\n", err);
    

      

    callback 函数 uart_cb 可能由多种事件触发。比如当接收到数据后会触发回调,并在参数 EVT 传递 UART_RX_RDY 和接收到的数据和长度。
    static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
    {
    	ARG_UNUSED(dev);
    
    	//LOG_INF("uart_cb evt->type:%d", evt->type);
    	switch (evt->type) {
    	case UART_TX_DONE:
    		printk("UART_TX_DONE\n");
    		break;
    
    	case UART_RX_RDY:
    		printk("UART_RX_RDY\n");
    		printk("received %d bytes\n", evt->data.rx.len);
    		break;
    
    	case UART_RX_DISABLED:
    		printk("UART_RX_DISABLED\n");
    		break;
    
    	case UART_RX_BUF_REQUEST:
    		printk("UART_RX_BUF_REQUEST\n");
    		uart_rx_buf_rsp(uart1, uart_rx_buf2, MAX_UART_BUF_LEN);
    		break;
    
    	case UART_RX_BUF_RELEASED:
    		printk("UART_RX_BUF_RELEASED\n");
    		break;
    
    	case UART_TX_ABORTED:
    		printk("UART_TX_ABORTED\n");
    		break;
    
    	default:
    		break;
    	}
    }
    

 

  • 我们在 DK 上把 TXD 引脚和 RXD 引脚短接来测试 UART 的收发,可以看到如下的 log 信息。UART 收到了自己发送的6字节的数据。 

  

UART 应用代码的优化

上面的 uart 演示代码中,我们只实现了简单的收发。下面我们将进一步在此基础上优化 UART 的收发代码。这一部分的修改都在 main.c 里,主要涉及下面几个部分:

  1. Thread 线程
  2. Semaphore 信号量
  3. 线程间通讯 Message queue

 

  • 线程 下面的代码中通过 K_THREAD_DEFINE 定义了 一个独立的线程来处理 uart 相关的代码。线程处理函数 uart_thread_task 中:也是先用 uart_callback_set 设置了回调函数;再用 uart_rx_enable 使能了接收;然后是一个 for 循环,在里面不断的接收消息,根据消息中的指令发送数据,或者处理接收到的数据。     
    #define UART_THREAD_STACK_SIZE    512
    #define UART_THREAD_PRIORITY      -1
    
    void uart_thread_task(void)
    {
    	int err;
    	struct uart_data_item_type uart_msgq;
    
    	k_sem_take(&uart_thread_start, K_FOREVER);
    
    	printk("uart_thread_task\n");
    
    	err = uart_callback_set(uart1, uart_cb, NULL);
    
    	err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS);
    	
    	for (;;) {
    		k_msgq_get(&uart_data_msgq, &uart_msgq, K_FOREVER);
    		printk("received uart data item\n");
    		
    		switch(uart_msgq.cmd) {
    		case UART_CMD_TX:
    		memcpy(uart_tx_buf,&uart_msgq.data, sizeof(uint32_t));
    		err = uart_tx(uart1, uart_tx_buf, sizeof(uint32_t), SYS_FOREVER_MS);
    		break;
    
    		case UART_CMD_DATA_PROCESS:
    		break;
    
    		default:
    			break;		
    		}
    	}
    }
    
    K_THREAD_DEFINE(uart_thread_id, UART_THREAD_STACK_SIZE, uart_thread_task, NULL, NULL,
    				NULL, UART_THREAD_PRIORITY, 0, 0);
    

    上面的代码中用 K_THREAD_DEFINE 定义线程的时候,需要指定此线程的优先级 UART_THREAD_PRIORITY。UART_THREAD_PRIORITY 的数据类型是 integer,可以是正数也可以是负数。优先级的数字越小,优先级越高,负数的优先级比正数高。thread 的优先级取值为负数时,此 thread 为协同线程 cooperative thread 。当这种线程正在执行的时候,其它更高优先级的线程不能打断它,必须等它执行完再执行下一个线程。当 thread 的优先级取值为正数,此 thread 为抢占线程 preemptible thread。当这种线程正在执行的时候,其它更高优先级的线程可以打断它,跳转到高优先级的任务。等高优先级的线程执行完才返回原 thread 继续执行。回到例程代码,从应用的角度出发,我们希望 uart_thread_task 的执行优先级大于 main 函数。通过查询文件 build\zephyr\.config 我们得知 CONFIG_MAIN_THREAD_PRIORITY 的取值为 0,也就是说 main thread 当前的优先级为 0, 所以我们定义了 UART_THREAD_PRIORITY 为 -1。这样 uart thread 的优先级就高于 main thread, 而且 uart thread 的执行不会被其它更高优先级的 thread 打断。需要注意的是这里的不能被打断只是对 thread 而言,中断是可以打断 cooperative thread 的。

  • 信号量 函数 uart_thread_task 的优先级比 main 函数高,所以会先于main 函数执行。如果之前的函数 uart_thread_task 里没有 k_sem_take(&uart_thread_start, K_FOREVER),就会出现如下图的现象。我们看到 uart thread 的 log 是先于 main thread 被打印出来的。 

     

    从应用的角度,我们希望 uart_thread_task  在 main 函数启动完广播之后再执行。这就引入了一个不同线程之间的同步问题。Zephyr RTOS 中可以通过 semaphore 解决不同 thread 间的同步问题。下面的代码中通过 K_SEM_DEFINE 定义了一个为 uart_thread_start 的 semaphore 。 函数 uart_thread_task 执行到函数 k_sem_take 时,如果 uart_thread_start 没有被释放,当前 thread 会被挂起等待,直到 semaphore 被释放。    
    static K_SEM_DEFINE(uart_thread_start, 0, 1);
    
    #define UART_THREAD_STACK_SIZE    512
    #define UART_THREAD_PRIORITY      -1
    
    void uart_thread_task(void)
    {
    	int err;
    	struct uart_data_item_type uart_msgq;
    
    	k_sem_take(&uart_thread_start, K_FOREVER);
    
    	printk("uart_thread_task\n");
    
    	err = uart_callback_set(uart1, uart_cb, NULL);
    
    	err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS);
    	
    	for (;;) {
    

      

    在 main 里通过 k_sem_give 释放 uart_thread_start。uart 线程会打断当前的 main thread 从 k_sem_take 继续执行。
    	err = spi_write_dt(&spim2_adc, &tx_bufs);
    	err = spi_write_dt(&spim2_flash, &tx_bufs);
    
    	k_sem_give(&uart_thread_start);
    
    	struct uart_data_item_type main_msgq;
    
    	main_msgq.cmd = UART_CMD_TX;
    	main_msgq.data = 0;
    
    	for (;;) {
    
    		while (k_msgq_put(&uart_data_msgq, &main_msgq, K_NO_WAIT) != 0) {
                /* message queue is full: purge old data & try again */
                k_msgq_purge(&uart_data_msgq);
            }
    		main_msgq.data++;
    		
    		dk_set_led(RUN_STATUS_LED, (++blink_status) % 2);
    		k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL));
    	}
    

 

  • 线程间通讯 演示代码中 main thread 会把要发送的数据通过线程通讯发送到  uart thread, uart thread 调用驱动函数发送。zephyr 中提供了多种线程间通讯方式,具体如下图,这里使用的是 message queue。下面的代码中 K_MSGQ_DEFINE 定义了一个名为 uart_data_msgq 的 message queue。uart_data_msgq 的缓冲区里最多可以容纳 8 个消息。
    struct uart_data_item_type {
        uint8_t cmd;
        uint32_t data;
    };
     
    K_MSGQ_DEFINE(uart_data_msgq, sizeof(struct uart_data_item_type), 8, 4);
    

      

    下面这段代码来自于 main thread 的 main 函数。代码会定时循环把待发送的数据打包成一个 message,然后推送到 message queue 里面。
        struct uart_data_item_type main_msgq;
    
        main_msgq.cmd = UART_CMD_TX;
        main_msgq.data = 0;
    
        for (;;) {
    
            while (k_msgq_put(&uart_data_msgq, &main_msgq, K_NO_WAIT) != 0) {
                /* message queue is full: purge old data & try again */
                k_msgq_purge(&uart_data_msgq);
            }
            main_msgq.data++;
           
            dk_set_led(RUN_STATUS_LED, (++blink_status) % 2);
            k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL));
        }
    

      

    下面的代码来自 uart thread 的 uart_thread_task 函数。 函数等待 message queue 里推送来的 message。得到 message 后,根据里面的 cmd 字段来处理发送或者接收数据。
    void uart_thread_task(void)
    {
    	int err;
    	struct uart_data_item_type uart_msgq;
    
    	k_sem_take(&uart_thread_start, K_FOREVER);
    
    	printk("uart_thread_task\n");
    
    	err = uart_callback_set(uart1, uart_cb, NULL);
    
    	err = uart_rx_enable(uart1, uart_rx_buf, MAX_UART_BUF_LEN, UART_RX_TIMEOUT_MS);
    	
    	for (;;) {
    		k_msgq_get(&uart_data_msgq, &uart_msgq, K_FOREVER);
    		printk("received uart data item\n");
    		
    		switch(uart_msgq.cmd) {
    		case UART_CMD_TX:
    		memcpy(uart_tx_buf,&uart_msgq.data, sizeof(uint32_t));
    		err = uart_tx(uart1, uart_tx_buf, sizeof(uint32_t), SYS_FOREVER_MS);
    		break;
    
    		case UART_CMD_DATA_PROCESS:
    		break;
    
    		default:
    			break;		
    		}
    	}
    }
    

      

    下面是加入线程间通讯的代码后得到的 log,当我们把 TX 和 RX 引脚短接后可以看出 uart thread 不断的发送从 main thread 传输的数据。                                                  

                                                 

 总结

本文从实际操作出发,介绍了用户最常用的一些外设如 GPIO, I2C, SPI, UART 的配置和使用方法。并介绍了一些简单 RTOS 组件的应用如 thread, semaphore, message queue。希望能帮助 Nordic 用户加快 nRF Connect SDK 的开发速度。                                                                                                                                                                                                                                                                                                                               

 

标签:msgq,thread,spi,uart,nRF,UART,user,Basic,SDK
From: https://www.cnblogs.com/victor-zheng/p/18470237

相关文章

  • 聊一聊坑人的 C# MySql.Data SDK
    https://www.cnblogs.com/huangxincheng/p/18619048 一:背景1.讲故事为什么说这东西比较坑人呢?是因为最近一个月接到了两个dump,都反应程序卡死无响应,最后分析下来是因为线程饥饿导致,那什么原因导致的线程饥饿呢?进一步分析发现罪魁祸首是 MySql.Data,这就让人无语了,并且反馈都......
  • 【THM】Wireshark The Basics(Wireshark基础-更新版)-学习
    本文相关的TryHackMe实验房间链接:https://tryhackme.com/room/wiresharkthebasics通过学习相关知识点:了解Wireshark的基础知识以及如何分析协议和PCAP文件。简介Wireshark是一款开源、跨平台的网络数据包分析工具,能够嗅探和调查实时流量并检查数据包捕获(PCAP)文件,它通常被用......
  • 【FAQ】HarmonyOS SDK 闭源开放能力 — IAP Kit(4)
    1.问题描述:发布了一个订阅,看日志显示订阅发布成功了,但是在消费的时候没有值,这个是什么原因?人脸活体检测返回上一页App由沉浸式变为非沉浸式多了上下安全区域。解决方案:对于公共事件来说就是提供这个能力,需要调用方保证时序,订阅成功之后再发广播才能收到。2.问题描述:微信支......
  • Easysearch Java SDK 2.0.x 使用指南(二)
    在上一篇文章中,我们介绍了EasysearchJavaSDK2.0.x的基本使用和批量操作。本文将深入探讨索引管理相关的功能,包括索引的创建、删除、开关、刷新、滚动等操作,以及新版SDK提供的同步和异步两种调用方式。SDK的对象构建有两种方式1.传统的Builder方式最基础的方式,像这......
  • OpenAI 推出嵌入式硬件 SDK,支持 ESP32 语音开发;INFP:音频驱动的双人对话头像生成,自动区
      开发者朋友们大家好: 这里是「RTE开发者日报」,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享RTE(Real-TimeEngagement)领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「有看点的会议」,但内容仅代表编辑的......
  • 【嵌入式Linux】---- 基于petaLinux和SDK开发的LED驱动和应用测试(全流程实战)
    1配置petaLinux环境变量在Linuxproject目录下,打开终端,输入命令source/opt/pkg/petalinux/2018.3/settings.sh2新建petaLinux工程petalinux-create-tproject--templatezynq-nZYNQ7010_LED3配置petaLinux工程输入cdZYNQ7010_LED,进入刚刚创建的工程文件;输入p......
  • 开源轻量级IM框架MobileIMSDK的鸿蒙NEXT客户端库已发布
    一、基本介绍MobileIMSDK-鸿蒙端是一套基于鸿蒙Next(纯血鸿蒙)系统的IM即时通讯客户端库:1)超轻量级(编译后库文件仅50KB)、无任何第3方库依赖(开箱即用);2)纯ArkTS编写、无Native代码、高度提炼、简单易用;3)基于鸿蒙Next标准WebSocket API,简洁优雅;4)可运行于任何支持鸿蒙Next的平台......
  • 项目1 Visual Basic 概述
    项目一:VisualBasic概述任务1认识VisualBasic1.项目导入ⅤisualBasic是一种面向对象的程序设计语言,从basic语言发展而来,在WINDOWS环境下运行。Visual:可视的Basic:basic语言2.特点面向对象和事件驱动的设计机制。(1)可视化(2)事件驱动(3)交互式(4)可扩充性3.版本介绍......
  • JAVA-通过大疆TSDK的API直接获取红外图片温度信息
    一、前言看过很多关于大疆红外图片用TSDK取温的方式,但是网上能搜到的大部分教程都是通过官方下载文件smple编译出来的程序来取温,如果这样做,虽然确实也能够实现目的,但不得不说,不但会降低运行速度,而且代码调用起来也麻烦。所以不如研究一下怎么直接调用他们的C++API。先说下大疆......
  • 【Basic Abstract Algebra】Exercises for Section 3.1 — Cosets and Lagrange's The
    Let\(G\)beafinitegroupand\(H<G\).If\([G:H]=2\),then\(gH=Hg\).Proof:If\([G:H]=2\),thenthereareonlytwocosetsof\(H\)in\(G\),andoneofthecosetsis\(H\)itself,i.e.,\[G=H\cupgH=H\cupHg,\]where\(H\cap......