IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> nRF52832学习记录(十一、TWI总线的应用 SHT21程序移植) -> 正文阅读

[嵌入式]nRF52832学习记录(十一、TWI总线的应用 SHT21程序移植)

1、nRF52xx TWI介绍

TWI总线基础介绍

nRF52系列处理器的I2C总线 叫做 TWI (I2C compatible two-wire interface),I2C兼容双线接口。I2C协议的基础知识我们这里就不过多的介绍,主要介绍一下nRF52系列处理器上的I2C资源。
在nRF52832处理器中,有2条 TWI 总线,TWI0 和 TWM1。
如果TWI 作为主机使用 带有 EasyDMA 则称为 TWIM。
如果TWI 作为从机使用 带有 EasyDMA 则称为 TWIS。

  • I2C兼容
  • 速率可达 100kbps,250kbps 或者 400kbps
  • 支持时钟延长
  • 带 EasyDmA

一次只能分配一个外设来驱动特定的GPIO引脚,同时为了确保在系统处于关闭模式时 TWI 主机所使用的引脚上正确的电平以及当TWI主机被禁用时,这些引脚必须按照下图所示进行配置:
在这里插入图片描述

TWI总线寄存器

11.杀

TWI 库函数介绍

在这一篇记录中,直接开一个小章节先熟悉一下需要用到的库函数

TWI初始化函数

nrf_drv_twi_init 有4个参数:
第一个参数选择使用哪个 TWI 模块(0或者1) 如果使用 EasyDMA 就是TWIM
第二个参数是 TWI 的配置,使用哪个 IO 口、频率、中断优先级等
第三个参数是 自定义的事件处理函数,如果设置为NULL, 则使能 TWI 的阻塞模式。
第四个参数 写 NULL暂时,不太清楚,也是处理函数?第三个写NULL,所以这个也设置为NULL

/*
第一个参数就是定义使用哪一个 TWI 或者是 TWIM(如果使用EasyDMA的话):
        typedef struct
        {
            uint8_t inst_idx;
            union
            {
        #ifdef TWIM_PRESENT
                nrfx_twim_t twim;
        #endif
        #ifdef TWI_PRESENT
                nrfx_twi_t  twi;
        #endif
            } u;
            bool    use_easy_dma;
        } nrf_drv_twi_t;

    nrf_drv_twi_t 结构体中的 nrfx_twi_t 结构体
        typedef struct
        {
            NRF_TWI_Type * p_twi;        ///< Pointer to a structure with TWI registers.
            uint8_t        drv_inst_idx; ///< Driver instance index.
        } nrfx_twi_t;

    nrfx_twi_t 结构体中的  NRF_TWI_Type 就是对应的 TWI 的每个寄存器

    第二个参数定义 TWI 的配置:

     typedef struct
    {
        uint32_t                scl;                 ///< SCL pin number.
        uint32_t                sda;                 ///< SDA pin number.
        nrf_drv_twi_frequency_t frequency;           ///< TWI frequency.
        uint8_t                 interrupt_priority;  ///< Interrupt priority.
        bool                    clear_bus_init;      ///< Clear bus during init.
        bool                    hold_bus_uninit;     ///< Hold pull up state on gpio pins after uninit.
    } nrf_drv_twi_config_t;

    第三个参数是 用户提供的事件处理程序:

    第四个参数是 传递给事件处理的上下文?
 
*/
ret_code_t nrf_drv_twi_init(nrf_drv_twi_t const *        p_instance,
                            nrf_drv_twi_config_t const * p_config,
                            nrf_drv_twi_evt_handler_t    event_handler,
                            void *                       p_context)

TWI使能函数

void nrf_drv_twi_enable(nrf_drv_twi_t const * p_instance) 就一个参数,和初始化函数的第一个参数一致,使用哪个 TWI 模块

TWI 主机发送数据给从机 函数

nrf_drv_twi_tx 有5个参数:
第一个参数,和上面介绍的一样,哪个 TWI 模块;
第二个参数 ,从机的地址;
注意从机地址 address,函数接收的是 7位地址,函数内部会自己加上读写位。
第三个参数,指向发送缓冲区的指针,发送的数据(一个字节);
第四个参数,发送数据的长度(发送几个一个字节的数据);
第五个参数, 如果设置了 stop 条件,在传输成功完成以后,总线上不会生成 stop 信号(允许在下一次传输中重复启动)。

/*
nrfx_err_t nrfx_twi_tx(nrfx_twi_t const * p_instance,
                       uint8_t            address,
                       uint8_t    const * p_data,
                       size_t             length,
                       bool               no_stop)
{
    nrfx_twi_xfer_desc_t xfer = NRFX_TWI_XFER_DESC_TX(address, (uint8_t*)p_data, length);

    return nrfx_twi_xfer(p_instance, &xfer, no_stop ? NRFX_TWI_FLAG_TX_NO_STOP : 0);
}
*/
ret_code_t nrf_drv_twi_tx(nrf_drv_twi_t const * p_instance,
                          uint8_t               address,
                          uint8_t const *       p_data,
                          uint8_t               length,
                          bool                  no_stop)
{
    ret_code_t result = 0;
    if (NRF_DRV_TWI_USE_TWIM)
    {
        result = nrfx_twim_tx(&p_instance->u.twim,
                                address, p_data, length, no_stop);
    }
    else if (NRF_DRV_TWI_USE_TWI)
    {
        result = nrfx_twi_tx(&p_instance->u.twi,
                               address, p_data, length, no_stop);
    }
    return result;
}

TWI 主机从从机读取 函数

nrf_drv_twi_rx 有4个参数:
第一个参数,哪个 TWI 设备;
第二个参数,从机的地址;
第三个参数,指向 保存读取数据的缓存区 的指针;
第四个参数,接收数据的长度(1个长度一个字节)

ret_code_t nrf_drv_twi_rx(nrf_drv_twi_t const * p_instance,
                          uint8_t               address,
                          uint8_t *             p_data,
                          uint8_t               length)

2、nRF52xx TWI 使用示例

今天我们直接使用 TWI库函数移植一下 STH21 温湿度传感器的代码,熟悉一下库函数的使用,以前在使用STM32的时候,常用的都是软件 I2C,就是用IO口模拟的I2C接口,当然如果你能够实现软件 I2C,就说明对 I2C 的原理比较熟悉了

SHT21 程序移植示例

使用硬件 I2C 在库函数中都有提供好的函数,还是比较简单的,我们现在分析一下以前的 I2C 读取程序( I2C的驱动部分就不分析了):

 	/* --------------------
    // measure temperature
    1、发送一个读取 温度 指令 cmd 给SHT21 
    2、等待传感器读取数据
    3、读取传感器的数据 ,3个字节数据
    */
    i2c_start();     // send start sequence (S)
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);//a write to slave 0x40 
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_MEAS_T);      //request to measure temperature	      
    i2c_stop();
	
    delay_ms(SHT2X_TEMP_MEAS_TIME);
            
    i2c_start();                 // send start sequence (SR)
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ); //  a read from slave 0x40
      
    if(u8Ack==I2C_ACK)  {
        aTemperature.raw = i2c_read(I2C_ACK)<<8;             // read hi byte 
        aTemperature.raw |= i2c_read(I2C_ACK);               // read lo byte
        aTemperature.crc = i2c_read(I2C_NACK);               // read check sum and finish transfere
    }else {
        aTemperature.raw = 0;                       
    }
    i2c_stop();

温度读取函数的步骤可以分为3步:

  1. 发送一个读取 温度 指令 cmd 给SHT21,那么我们在 nRF52832 上面使用库函数怎么实现,使用nrf_drv_twi_tx函数,刚开始的时候,我通过 mpu6050 的驱动修改,发现问题,后面我们会分析什么问题,使用这个函数只需要一句话:
    err_code = nrf_drv_twi_tx(&sht_twi, SHT2X_SLAVEADDRESS, tx_buf, 1, true);
    其中 tx_buf 就是需要发送的 cmd 命令;
  2. 等待传感器读取数据
  3. 读取传感器的数据 ,3个字节数据,读取的话我们使用 nrf_drv_twi_rx 函数,读取3个字节:err_code = nrf_drv_twi_rx(&sht_twi, SHT2X_SLAVEADDRESS, destination, 3);
    其中 destination 是你需要保存的缓冲区的地址;

最后移植成功的的 sht21.c 程序如下:

/****************************************Copyright (c)************************************************
sht21 移植程序
2021/10/5  by qzh
根据手册,sht21读取的时候并不需要指定读取特定寄存器的地址
**---------------------------------------------------------------------------------------------------*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "nrf_drv_twi.h"
#include "sht21.h"

struct {
	  sint16 value;
	  uint16 raw;
	  uint8  crc;
} aTemperature, aHumidity;

//TWI驱动程序实例ID,ID和外设编号对应,0:TWI0  1:TWI1
#define TWI_INSTANCE_ID     0
//TWI传输完成标志
static volatile bool m_xfer_done = false;
//定义TWI驱动程序实例,名称为sht_twi
static const nrf_drv_twi_t sht_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

//TWI事件处理函数
void sht_twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
    //判断TWI事件类型
	switch (p_event->type)
    {
        //传输完成事件
		case NRF_DRV_TWI_EVT_DONE:
            m_xfer_done = true;//置位传输完成标志
            break;
        default:
            break;
    }
}

//TWI初始化
void twi_sht21_init(void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t twi_config = {
       .scl                = SHT_SCL,  //定义TWI SCL引脚
       .sda                = SHT_SDA,  //定义TWI SDA引脚
       .frequency          = NRF_DRV_TWI_FREQ_100K, //TWI速率
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH, //TWI优先级
       .clear_bus_init     = false//初始化期间不发送9个SCL时钟
    };

    err_code = nrf_drv_twi_init(&sht_twi, &twi_config, sht_twi_handler, NULL);
	//检查返回的错误代码
    APP_ERROR_CHECK(err_code);
    //使能TWI
    nrf_drv_twi_enable(&sht_twi);
    printf("twi_master_init ok.\r\n"); 
}
 
/*
在网上参考的I2C设备扫描程序
如果在非阻塞模式下面(目前Demo使用的模式),这个函数直接用会有点问题,出一些错误
如果加上while (m_xfer_done == false){},会“卡死”,出不去
****
如果使用阻塞模式,下面这个行数能够在正常执行,能够获取到 I2C 设备的地址
所以一般使用建议使用阻塞模式
****
*/
void iic_scan_address(void)
{ 
    ret_code_t err_code;
    uint8_t address;
    uint8_t sample_data;
    bool detected_device = false;
    printf("TWI scanner started.\r\n");
    
    twi_sht21_init();//twi init
    nrf_delay_ms(2000);
    for (address = 1; address <= 127; address++)
    {
        err_code = nrf_drv_twi_rx(&sht_twi, address, &sample_data, sizeof(sample_data));
        if (err_code == NRF_SUCCESS)
        {
            detected_device = true;
            printf("TWI-i2c device detected at address 0x%x.\r\n", address);

        }
    }
    if (!detected_device) 
    {
        printf("No device was found.\r\n");
    }
    printf("TWI device scan ended.\r\n");
}

//    fT  = 175.72*u16T/65536.0 - 46.85;
//    fRH = 125.0*u16RH/65536.0 - 6.0;
// -------------------------------------------------------------------
sint16 sht21_calcRH(uint16 u16RH)
{
  sint16 humidityRH;              // variable for result

  u16RH &= ~0x0003;          // clear bits [1..0] (status bits)
  //-- calculate relative humidity [%RH] --

  humidityRH = (sint16)(-600 + (12500*(sint32)u16RH)/65536 ); // RH = -6 + 125 * SRH/2^16
  return humidityRH;                                          // Return RH*100
}

sint16 sht21_calcTemperature(uint16 u16T)
{
  sint16 temperature;            // variable for result

  u16T &= ~0x0003;           // clear bits [1..0] (status bits)

  //-- calculate temperature [癈] --
  temperature= (sint16)(-4685 + (17572*(sint32)u16T)/65536); //T = -46.85 + 175.72 * ST/2^16
  return temperature;                                        //return T*100
}

/*
读取函数,使用库函数,I2C_start 和 ack 会自动处理
注意sht21 的手册, 只需要对 设备地址 写数据 读数据,没有额外特定的寄存器
*/
//bool sht_write(uint8_t register_address, uint8_t value) //这个函数使用需要在需要读取特定的寄存器
bool sht_write(uint8_t cmd)
{
    ret_code_t err_code;
	uint8_t tx_buf[1];

    //准备写入的数据
	// tx_buf[0] = register_address;
    // tx_buf[1] = value;
    tx_buf[0] = cmd;

    m_xfer_done = false;

    err_code = nrf_drv_twi_tx(&sht_twi, SHT2X_SLAVEADDRESS, tx_buf, 1, true);

	while (m_xfer_done == false){}

	if (NRF_SUCCESS != err_code)
    {
        return false;
    }
	return true;	
}

/*
读取函数,
注意SHT21,等待时间直接读取就可以
现在的很多I2C传感器作为从设备,读取的步骤:
1、主机 先写一个 寄存器 地址发送给从设备,所以在读函数里面还是得先调用一次写;
2、然后 主机再 去按照读取的数据大小读取

SHT21 不需要!! 注意代码中注释掉的部分
*/
//bool sht_read(uint8_t register_address, uint8_t * destination, uint8_t number_of_bytes)
bool sht_read(uint8_t * destination, uint8_t number_of_bytes)
{
    ret_code_t err_code;
    //TWI传输完成标志设置为false
    // m_xfer_done = false;
    // err_code = nrf_drv_twi_tx(&sht_twi, SHT2X_SLAVEADDRESS, &register_address, 1, true);
    // //等待TWI总线传输完成
    // while (m_xfer_done == false){}
    // if (NRF_SUCCESS != err_code)
    // {
    //     return false;
    // }
    //TWI传输完成标志设置为false
    m_xfer_done = false;
    err_code = nrf_drv_twi_rx(&sht_twi, SHT2X_SLAVEADDRESS, destination, number_of_bytes);
    //等待TWI总线传输完成
    while (m_xfer_done == false){}
    if (NRF_SUCCESS != err_code)
    {
        return false;
    }
    return true;
}

void SHT2X_THMeasure() {

    uint8 t_value[3];
    uint8 h_value[3];

    #if (SHT2X_RESOLUTION != 0x00)		                                      // only needed if used resolution other than default
    i2c_start();                                                        // send start sequence (S)
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);               // write to slave 0x40
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_RD_REG);                     // request to read from user register  
      
    i2c_start();                                                      	// send start sequence (S)
    u8Ack = (u8Ack<<1)|i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);     // read from slave 0x40
    u8UserReg = i2c_read(I2C_NACK);                                   	// read user register       
      
    i2c_start();                                                      	// send start sequence (S)  
    u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);               // write to slave 0x40
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_WR_REG);                     // request to write user register
    u8Ack = (u8Ack<<1)|i2c_write(SHT2X_RESOLUTION | (u8UserReg & ~0x81)); // write new user register data
    #endif//(SHT2X_RESOLUTION != 0x00)	
    // --------------------
    // measure temperature
    // --------------------
    // i2c_start();     // send start sequence (S)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);//a write to slave 0x40 
    // u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_MEAS_T);      //request to measure temperature	      
    // i2c_stop();

    //sht_write(SHT2X_SLAVEADDRESS,SHT2X_CMD_MEAS_T);  //发送一条数据就可以等待测量
    sht_write(SHT2X_CMD_MEAS_T);

	nrf_delay_ms(SHT2X_TEMP_MEAS_TIME);
    // delay_ms(SHT2X_TEMP_MEAS_TIME);
    //time_wait(SHT2X_TEMP_MEAS_TIME);
            
    // i2c_start();                 // send start sequence (SR)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);  //a read from slave 0x40
      
    // if(u8Ack==I2C_ACK)  {
    //     aTemperature.raw = i2c_read(I2C_ACK)<<8;                        // read hi byte 
    //     aTemperature.raw |= i2c_read(I2C_ACK);                          // read lo byte
    //     aTemperature.crc = i2c_read(I2C_NACK);                          // read check sum and finish transfere
    // }else {
    //     aTemperature.raw = 0;                       
    // }
    // i2c_stop();
    // sht_read(SHT2X_SLAVEADDRESS,t_value,3);
    // nrf_delay_ms(2);
    // aTemperature.raw = t_value[0]<<8;
    // aTemperature.raw |= t_value[1];
    // aTemperature.crc = t_value[2]; 
    if(sht_read(t_value,3)== true){
        aTemperature.raw = t_value[0]<<8;
        aTemperature.raw |= t_value[1];
        aTemperature.crc = t_value[2];  
    }else{
        aTemperature.raw = 0;  
    }
    // -------------------------
    // Humidity Measure 
    // -------------------------
    // i2c_start();                                                      // send start sequence (S)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_WRITE);             // a write to slave 0x40      		 1000 0000
    // u8Ack = (u8Ack<<1)|i2c_write(SHT2X_CMD_MEAS_RH);                  // request to measure humidity F5  1110 0101
	// i2c_stop();
				
	//sht_write(SHT2X_SLAVEADDRESS,SHT2X_CMD_MEAS_RH);  //发送一条数据就可以等待测量
    sht_write(SHT2X_CMD_MEAS_RH);
	nrf_delay_ms(SHT2X_HUMI_MEAS_TIME);	
	// shortTermSleep(SHT2X_HUMI_MEAS_TIME);
    // delay_ms(SHT2X_HUMI_MEAS_TIME);
				
    // i2c_start();                                                      // send start sequence (SR)
    // u8Ack = i2c_write((SHT2X_SLAVEADDRESS<<1)|I2C_READ);              // read from slave 0x40     			 1000 0001

    // if(u8Ack==I2C_ACK)  {                                             // timeout
    //     aHumidity.raw = i2c_read(I2C_ACK)<<8;                           // read hi byte 
    //     aHumidity.raw |= i2c_read(I2C_ACK);                             // read lo byte
    //     aHumidity.crc = i2c_read(I2C_NACK);                           // read check sum and finish transfere
    // }else{
    //     aHumidity.raw = 0;
    // }
	// i2c_stop();

    if(sht_read(h_value,3)== true){
        aHumidity.raw = h_value[0] <<8;                           // read hi byte 
        aHumidity.raw |= h_value[1];                             // read lo byte
        aHumidity.crc = h_value[2];                           // read check sum and finish transfere 
    }else{
        aTemperature.raw = 0;  
    }				   	
    aTemperature.value = sht21_calcTemperature(aTemperature.raw);
    aHumidity.value = sht21_calcRH(aHumidity.raw);      // signed value, temperature = aTemperature.value * 0.01?
    if(aTemperature.crc!= sht21_CRC((uint8*)&aTemperature.raw, 2))  {}
    if(aHumidity.crc!= sht21_CRC((uint8*)&aHumidity.raw, 2))  {}
    if(aTemperature.value>5100) aTemperature.value=5100;              //prevent temperature over-/underflow
    else if(aTemperature.value<0) aTemperature.value=0;
    if(aHumidity.value>10000) aTemperature.value=10000;              //prevent temperature over-/underflow
    else if(aTemperature.value<0) aTemperature.value=0;
}

uint16_t getTemperature() {
  return aTemperature.value;
}

uint16_t getHumidity() {
  return aHumidity.value;
}

//==============================================================================
uint8 sht21_CRC(uint8 value[], uint8 u8Bytes) {
//  CRC
    const uint16 POLYNOMIAL = 0x131;  //P(x)=x^8+x^5+x^4+1 = 100110001
    uint8 crc = 0;	
    uint8 byteCtr;
    uint8 bitCtr;

    //calculates 8-Bit checksum with given polynomial
    for (byteCtr = 0; byteCtr < u8Bytes; ++byteCtr) { 
        crc ^= (value[byteCtr]);
        for (bitCtr = 8; bitCtr > 0; --bitCtr)
        { 
          if (crc & 0x80) crc = (crc << 1) ^ POLYNOMIAL;
          else crc = (crc << 1);
        }
    }
    return crc;
}  

额外说明,上面的 TWI 初始化使用了非阻塞模式,如果使用阻塞模式
1、初始化函数变成
err_code = nrf_drv_twi_init(&sht_twi, &twi_config, NULL, NULL);
2、去掉事件处理函数
// void sht_twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context){...}
3、相应的位置(读写函数中读写数据后)去掉标志位的等待
// while (m_xfer_done == false){}

SHT21 移植问题分析

在 SHT21 移植过程中,开始按照 MPU6050 的程序修改的写函数 和 读函数如下:
bool sht_write(uint8_t register_address, uint8_t value)
bool sht_read(uint8_t register_address, uint8_t * destination, uint8_t number_of_bytes)
然后怎么都出来数据 ……

其实通过 SHT21 以前的程序,不需要指定特定的寄存器写命令和读取数据 ,就能发现和MPU6050的不同
在传感器使用手册中,关于SHT21 使用非主机模式读取 的例子:
在这里插入图片描述
我们再来看看 MPU6050 作为从机被写图示:
在这里插入图片描述
MPU6050 作为从机读取图示:
在这里插入图片描述
通过比较 SHT21 和 MPU6050 读写逻辑,我们可以直观的发现不同之处,所以最后将读写的函数由
bool sht_write(uint8_t register_address, uint8_t value)
bool sht_read(uint8_t register_address, uint8_t * destination, uint8_t number_of_bytes)
改成了
bool sht_write(uint8_t cmd)
bool sht_read(uint8_t * destination, uint8_t number_of_bytes)
具体的实现逻辑可以看上面给出的 sht21.c 程序

最后在主函数中调用

  while(true)
  {
    SHT2X_THMeasure();
    T=(float)getTemperature();
 	H=(float)getHumidity();
    T=(getTemperature()/100.0);
    H=(getHumidity()/100.0);      //OK
    printf("\r\n%4.2f C\r\n%4.2f%%\r\n",T,H);
    nrf_delay_ms(2000);
  }

使用阻塞模式最后的测试效果:
在这里插入图片描述

SHT30 程序(待更新)

SHT30 等项目上开始使用的时候,来更新一下代码

MPU6050 程序(待更新)

同上

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-10-06 12:23:33  更:2021-10-06 12:25:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 4:53:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码