《ESP32-S3使用指南—MicroPython版 V1.0》第十四章 IO扩展实验

第十四章 IO扩展实验


       在正点原子ESP32-S3开发板中,搭载了一颗16位的IO扩展芯片,该芯片主要用于控制输出和获取用户的输入,然后通过IIC协议传输至MCU内核。在本章中,我们将介绍如何通过自编写MicroPython模块来驱动该芯片,并实现按键控制蜂鸣器和LED开关状态的功能。

       14.1 IIC简介&XL9555简介

       14.2 XL9555 C模块解析

       14.3 硬件设计

       14.4 软件设计

       14.5 下载验证


        14.1 IIC简介 &XL9555简介


       14.1.1 IIC简介

       IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器以及其外围设备。它是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据,在CPU与被控IC之间、IC与IC之间进行双向传送。

       IIC总线有如下特点:

       ①总线由数据线SDA和时钟线SCL构成的串行总线,数据线用来传输数据,时钟线用来同步数据收发。

       ②总线上每一个器件都有一个唯一的地址识别,所以我们只需要知道器件的地址,根据时序就可以实现微控制器与器件之间的通信。

       ③数据线SDA和时钟线SCL都是双向线路,都通过一个电流源或上拉电阻连接到正的电压,所以当总线空闲的时候,这两条线路都是高电平。

       ④总线上数据的传输速率在标准模式下可达100kbit/s在快速模式下可达400kbit/s在高速模式下可达3.4Mbit/s。

       ⑤总线支持设备连接。在使用IIC通信总线时,可以有多个具备IIC通信能力的设备挂载在上面,同时支持多个主机和多个从机,连接到总线的接口数量只由总线电容400pF的限制决定。IIC总线挂载多个器件的示意图,如下图所示。


图14.1.1.1 IIC总线挂载多个器件


       下面来学习IIC总线协议,IIC总线时序图如下所示:


图14.1.1.2 IIC总线时序图


       为了便于大家更好的了解IIC协议,我们从起始信号、停止信号、应答信号、数据有效性、数据传输以及空闲状态等6个方面讲解,大家需要对应图17.1.1.2的标号来理解。

       ① 起始信号

       当SCL为高电平期间,SDA由高到低的跳变。起始信号是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在起始信号产生后,总线就处于被占用状态,准备数据传输。

       ② 停止信号

       当SCL为高电平期间,SDA由低到高的跳变。停止信号也是一种电平跳变时序信号,而不是一个电平信号。该信号由主机发出,在停止信号发出后,总线就处于空闲状态。

       ③ 应答信号

       发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

       观察上图标号③就可以发现,有效应答的要求是从机在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主机,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主机接收器发送一个停止信号。

       ④ 数据有效性

       IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。数据在SCL的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。

       ⑤ 数据传输

       在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。

       ⑥ 空闲状态

       IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

       了解前面的知识后,下面介绍一下IIC的基本的读写通讯过程,包括主机写数据到从机即写操作,主机到从机读取数据即读操作。下面先看一下写操作通讯过程图,如下图所示.


图14.1.1.3 写操作通讯过程图


       主机首先在IIC总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地址+0(写操作)组成的8bit数据,所有从机接收到该8bit数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

       接着讲解一下IIC总线的读操作过程,先看一下读操作通讯过程图,如下图所示。


图14.1.1.4 读操作通讯过程图

       主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,都是由主机发出起始信号,接着发送从机地址+1(读操作)组成的8bit数据,从机接收到数据验证是否是自身的地址。 那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回8bit数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据。

       24C02的数据传输时序是基于IIC总线传输时序,下面讲解一下24C02的数据传输时序。


       14.1.2 XL9555简介

       XL9555是信路达推出的一款24引脚CMOS器件,通过I²C总线/SMBus接口(串行时钟线(SCL)、串行数据线(SDA))为大多数微控制器系列提供16位通用并行输入/输出(GPIO)扩展。这些设备设计用于2.3-V至5.5-V VCC操作。这些改进包括更高的驱动能力、5V I/O容差、更低的电源电流、单独的I/O配置和更小的封装。当ACPI电源开关、传感器、按钮、LED、风扇等需要额外的I/O时,I/O扩展器提供了一个简单的解决方案。当任何输入状态与其对应的输入端口寄存器状态不同时,这些设备开漏中断(/INT)输出被激活,并用于向系统主机指示输入状态已改变。在超时或其他不当操作的情况下,系统主设备可以利用上电重置功能重置这些设备。通电重置将寄存器设置为默认值,并初始化I²C/SMBus状态机。三个硬件引脚(A0、A1和A2)改变固定的I²C总线地址,并允许多达八个设备共享同一I²C总线/SMBus。

       XL9555有如下特性:

       l I²C总线至16位GPIO扩展器

       l 工作电源电压范围为2.3 V至5.5 V

       l 低待机电流消耗

       l 5 V容错I/O端口

       l 400 kHz快速模式I²C总线时钟频率

       l SCL/SDA输入上的噪声滤波器

       l 内部通电复位

       l 通电时无故障

       l 极性反转寄存器

       l 开漏有源低中断输出

       l 16个I/O引脚,默认为16个输入


       1,引脚说明

       XL9555的引脚说明如下表所示。


表14.1.2.1 XL9555引脚说明


       我们使用的XL9555采用QFN24封装,总共24个脚,其中包括:16个准双向IO口(P00~P07,P10~P17)、3个地址线(A0~A2)、SCL、SDA、INT、GND和Vcc。每个XL9555只需要最少2个IO口,就可以扩展16路IO,在MCU的IO不够用的时候,XL9555是一个非常不错的IO扩展方案。


       2,寻址

       一个IIC总线上,可以挂多个XL9555(通过A0~A2寻址),XL9555的从机地址格式如下图所示。


图14.1.2.1 XL9555从机地址格式


       图中的A0~A2为XL9555的寻址信息,我们开发板上A0~A2都是接GND的,所以,XL9555的地址为:0X20(左移了一位);R/W为读/写控制位,R/W=0的时候,表示写数据到XL9555,用来使IO输出电平;R/W=1的时候,表示读取XL9555的数据,获取IO口的状态。相关内容,请参考《XL9555.pdf》规格书。


       3,芯片寄存器地址

       在成功确认地址字节之后,总线主机发送一个命令,该命令指向存储在设备的控制XL9555寄存器中的字节。这个数据的低三位表示操作不同的寄存器,例如,操作读和写和内部寄存器(输入、输出、极性反转和配置)。如下图所示。


图14.1.2.2 芯片寄存器地址


       从上图可以看出,“Input Port0”和“Input Port1”寄存器用于获取端口0和端口1的IO输入状态;“Output Port0”和“Output Port1”寄存器用于配置端口0和端口1的IO输出电平;“Polarity Inversion Port0”和“Polarity Inversion Port1”用于对端口0和端口1进行极性翻转,例如,如果P00输入的电平为低电平,那么XL9555将对它进行极性翻转,因此MCU获得的是高电平状态,同理,输出一样的原理;“Configuration Port1”和“Configuration Port0”寄存器则用于配置端口0和端口1的IO输入/输出模式。

       值得注意的是,“Configuration Port1”和“Configuration Port0”寄存器配置需结合实际的原理图来设置,如下图所示。


图14.1.2.3 XL9555硬件原理图


       从上图可以看到,IO0_0~IO0_1和IO1_4~IO1_7被用作输入IO,其他管脚被用作输出IO。根据这些信息,我们使用寄存器6和寄存器7来配置端口0和端口1的IO输入/输出状态。


       3,配置IO输入输出模式

       XL9555的配置输入输出模式的写数据时序如下图所示。


图14.1.2.4 寄存器6和寄存器7写数据流程


       由图可知,XL9555操作寄存器6和寄存器7的数据写入非常简单,首先发送XL9555的从机地址+写信号(R/W=0),然后等待XL9555的应答信号,在应答成功后,发送数据(DATA0:用来配置寄存器6的IO输入输出模式)给XL9555就可以了,发送完数据,会收到XL9555的应答信号,接着,再一次发送数据(DATA1:用来配置寄存器7的IO输入输出模式)并且接收到XL9555的应答信号,最后就是IIC结束信号。


       4,写数据(输出状态)

       XL9555的配置输出写数据时序如下图所示。


图14.1.2.5 寄存器2和寄存器3写数据流程


       由图可知,首先发送XL9555的从机地址+写信号(R/W=0),然后等待XL9555的应答信号,在应答成功后,发送数据(DATA0:用来配置寄存器2的IO输出状态)给XL9555就可以了,发送完数据,会收到XL9555的应答信号,接着,再一次发送数据(DATA1:用来配置寄存器3的IO输出状态)并且接收到XL9555的应答信号,最后就是IIC结束信号。


       5,读数据(输入状态)

       XL9555的读数据时序如下图所示:



       XL9555的读数据流程:首先发送XL9555的从机地址+读信号(R/W=1),然后等待XL9555应答(注意:XL9555在发送应答的同时,会锁存P00~P07和P10~P17的数据),然后读取P00~P07的数据,再读取P10~P17的数据。数据读取支持连续读取,在最后的时候发送STOP信号,即可完成读数据操作。


       6,中断

       XL9555带有中断输出脚,它可以连接到MCU的中断输入引脚上。在输入模式中(IO口输出高电平,即可做输入使用),输入的上升沿或下降沿都可以产生中断,在tiv时间之后INT有效。特别注意:初始化XL9555以及一旦中断有效后,必须对XL9555进行一次读取/写入操作,复位中断,才可以输出下一次中断,否则中断将一直保持(无法输出下一次输入信号变化所产生的中断)。


        14.2 XL9555 C模块解析


       14.2.1 C模块解析

       在先前的章节里,作者已经详述了C模块的添加流程以及整体架构,接下来作者将简要介绍正点原子XL9555 C模块驱动。这个讲解内容会分为几个部分:XL9555构造函数、写入数据、读取数据。XL9555 C模块驱动可在A盘à6,软件资料à1,软件à2,MicroPython开发工具à01-Windowsà2,正点原子MicroPython驱动àCModules_LibàIIC路径下找到。


       1,XL9555构造函数

/* xl9555结构体 */
typedef struct _xl9555_obj_t
{
    mp_obj_base_t   base;       /* 基地址 */
    mp_obj_base_t   *iic_obj;   /* 指向IIC控制块 */
} xl9555_obj_t;
 
mp_obj_t xl9555_make_new(const mp_obj_type_t *type,size_t n_args,
size_t n_kw,const mp_obj_t *all_args )
{
    /* 创建对象的参数 */
    enum
    {
        ARG_iic,
    };
 
    static const mp_arg_t allowed_args[] = {
        { MP_QSTR_iic, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} },
    };
    mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args,
 MP_ARRAY_SIZE(allowed_args), allowed_args, args);
 
    /* 创建对象 */
    xl9555_self = m_new_obj(xl9555_obj_t);
    xl9555_self->base.type = &xl9555_type;
    /* 设置对象参数 */
mp_obj_base_t *xl9555_obj= 
(mp_obj_base_t*)MP_OBJ_TO_PTR(args[ARG_iic].u_obj);
 
    if (xl9555_obj == MP_OBJ_NULL)
    {
        mp_raise_ValueError(MP_ERROR_TEXT("I2C init ???"));
    }
 
    xl9555_self->iic_obj        = xl9555_obj;
 
    if (xl9555_obj == MP_OBJ_NULL)
    {
        mp_raise_ValueError(MP_ERROR_TEXT("I2C init ???"));
    }
/* 初始化XL9555 */
    xl9555_init();
    
    return MP_OBJ_FROM_PTR(xl9555_self);
}

       从上述源代码中可以得知,该构造函数只有一个参数,即传入IIC驱动的控制块。我们可以通过这个控制块调用IIC驱动下的收发函数。然后,我们还创建了一个XL9555对象,用于实例化对象并引用类的方法。最后,调用了xl9555_init函数来初始化XL9555 IO扩展芯片。以下是该初始化代码的示例:

/**
 * @brief       XL9555初始化
 * @param       无
 * @retval      无
 */
void xl9555_init(void)
{
    /* 上电先读取一次清除中断标志 */
    uint8_t r_data[2];
    xl9555_read_byte(r_data, 2);
    
    /* 配置XL9555端口,即写配置寄存器,数据格式为:地址 + CMD + 6 input + 12 output */
    xl9555_ioconfig(0xF003);
    xl9555_pin_write(BEEP_IO,1);
    xl9555_pin_write(SPK_EN_IO,1);
}
 
/**
 * @brief       XL9555的IO配置
 * @param       config_value:配置数值
 * @retval      返回配置数值
 */
static uint16_t xl9555_ioconfig(uint16_t config_value)
{
    /* 从机地址 + CMD + data1(P0) + data2(P1) */
/* P00、P01、P14、P15、P16、P17为输入,其他引脚为输出
-->1111 0000 0000 0011 .注意:0为输出,1为输入*/
    uint8_t data[2];
    esp_err_t err;
    int retry = 3;
 
    data[0] = (uint8_t)(0xFF & config_value);
    data[1] = (uint8_t)(0xFF & (config_value >> 8));
 
    do
    {
        err = xl9555_write_byte(XL9555_CONFIG_PORT0_REG, data, 2);
        
        if (err != ESP_OK)
        {
            retry--;
            mp_hal_delay_ms(100); 
            ESP_LOGE("IIC", "%s configure %X failed, ret: %d", 
__func__, config_value, err);
            xl9555_failed = 1;
            
            if ((retry <= 0) && xl9555_failed)
            {
                mp_hal_delay_ms(5000); 
                esp_restart();
            }
        }
        else
        {
            xl9555_failed = 0;
            break;
        }
        
    } while (retry);
    
    return config_value;
}

       我们可以看到,首先,作者上电先读取一次数据清除中断标志,然后调用xl9555_ioconfig函数配置P0端和P1端的管脚输入输出模式,最后,调用xl9555_pin_write函数关闭蜂鸣器和喇叭功能。


       2,XL9555写时序

/**
 * @brief       向XL9555写入16位IO值
 * @param       data:存储区
 * @param       len :读取数据大小
 * @retval      ESP_OK:读取成功;其他:读取失败
 */
esp_err_t xl9555_write_byte(uint8_t reg,  uint8_t* data, size_t len)
{
    int data_len = 0;
    mp_obj_base_t *self = (mp_obj_base_t *)MP_OBJ_TO_PTR(xl9555_self->iic_obj);
mp_machine_i2c_p_t *i2c_p = 
(mp_machine_i2c_p_t *)MP_OBJ_TYPE_GET_SLOT(self->type, protocol);
 
    mp_machine_i2c_buf_t bufs[2] = {
        {.len = 1, .buf = ®},
        {.len = len, .buf = data},
    };
 
data_len = i2c_p->transfer(self, XL9555_ADDR,2,
bufs, MP_MACHINE_I2C_FLAG_STOP);
    
    if (data_len != 0)
    {
        return ESP_OK;
    }
    else
    {
        return ESP_FAIL;
    }
}

       在上述源代码中,作者根据传入的IIC控制块,调用了IIC收发函数来发送XL9555的命令和数据。发送完成后,函数返回了ESP_OK状态。


       3,XL9555读时序

/**
 * @brief       读取XL9555的16位IO值
 * @param       data:存储区
 * @param       len :读取数据大小
 * @retval      ESP_OK:读取成功;其他:读取失败
 */
esp_err_t xl9555_read_byte(uint8_t* data, size_t len)
{
    int data_len = 0;
    uint8_t memaddr_buf[1];
    memaddr_buf[0]  = XL9555_INPUT_PORT0_REG;
    mp_obj_base_t *self = (mp_obj_base_t *)MP_OBJ_TO_PTR(xl9555_self->iic_obj);
mp_machine_i2c_p_t *i2c_p = 
(mp_machine_i2c_p_t *)MP_OBJ_TYPE_GET_SLOT(self->type, protocol);
 
    mp_machine_i2c_buf_t bufs[2] = {
        {.len = 1, .buf = memaddr_buf},
        {.len = len, .buf = data},
    };
 
data_len = i2c_p->transfer(self, XL9555_ADDR, 2, bufs,
MP_MACHINE_I2C_FLAG_WRITE1
| MP_MACHINE_I2C_FLAG_READ
| MP_MACHINE_I2C_FLAG_STOP);
    
    if (data_len != 0)
    {
        return ESP_OK;
    }
    else
    {
        return ESP_FAIL;
    }
}

       同样地,XL9555的读时序也是利用IIC收发函数来实现的。写时序和读时序的唯一区别在于最后的flag标志位不同,从而导致发送流程有所不同。如果读者想了解i2c_p->transfer函数的收发流程,可以在MicroPython源代码中找到machine_i2c.c文件(位于micropython\ports\esp32路径下)。


       4,控制某个IO的状态

/**
 * @brief       控制所有IO的电平
 * @param       reg_value:寄存器的数值
 * @retval      返回16位IO状态
 */
uint16_t xl9555_multi_write_ex(uint16_t reg_value)
{
    uint8_t w_data[2];
    esp_err_t err;
    int retry = 3;
 
    w_data[0] = (uint8_t)(0xFF & reg_value);
    w_data[1] = (uint8_t)(0xFF & (reg_value >> 8));
 
    do
    {
        /* 控制某个IO的电平 */
        err = xl9555_write_byte(XL9555_OUTPUT_PORT0_REG, w_data, 2);
        
        if (err != ESP_OK)
        {
            retry--;
            mp_hal_delay_ms(100);
            ESP_LOGE("IIC", "%s failed, prev:%X, target: %X", 
__func__, xl9555_value, reg_value);
            xl9555_failed = 1;
        }
        else
        {
            xl9555_failed = 0;
            break;
        }
        
    } while (retry);
 
    if (xl9555_failed == 0)
    {
        xl9555_value = reg_value;
    }
    
    return xl9555_value;
}
 
/**
 * @brief       控制某个IO的电平
 * @param       pin:控制的IO
 * @param       val:电平
 * @retval      返回16位IO状态
 */
uint16_t xl9555_pin_write(uint16_t pin, int val)
{
    uint16_t i;
    uint16_t wrt_data = xl9555_value;
    uint16_t bitval = (val > 0) ? 1 : 0;
    uint16_t temp;
 
    for (i = 0; i < 16; i++)
    {
        temp = 0x1 << i;
 
        if (pin & temp)
        {
            if (val) wrt_data |= temp;
            else wrt_data &= ~temp;
        }
    }
 
    return xl9555_multi_write_ex(wrt_data);
}

       上述源码中,作者先判断控制哪个IO,然后调用xl9555_multi_write_ex函数控制这个IO的电平输出。


       5,读取IO状态

/**
 * @brief       读取XL9555的所有IO状态
 * @param       无
 * @retval      返回16位IO状态
 */
uint16_t xl9555_multi_read_ex(void)
{
    uint16_t ret;
    esp_err_t err;
    uint8_t r_data[2];
    int retry = 3;
 
    do
    {
        err = xl9555_read_byte(r_data, 2);
        
        if (err != ESP_OK)
        {
            retry--;
            mp_hal_delay_ms(100);
            ESP_LOGE("IIC", "%s failed, prev:%X, target: %X", 
__func__, xl9555_value, xl9555_readonly_value);
            xl9555_value = 1;
        }
        else
        {
            xl9555_value = 0;
            break;
        }
        
    } while (retry);
 
    ret = r_data[1] << 8 | r_data[0];
 
    xl9555_readonly_value = ret;
    return ret;
}
 
/**
 * @brief       获取某个IO状态
 * @param       pin:要获取状态的IO
 * @retval      返回IO口的状态(0/1)
 */
int xl9555_pin_read(uint16_t pin)
{
    return (xl9555_multi_read_ex() & pin) ? 1 : 0;
}

       从上述源代码中可以得知,作者首先调用了xl9555_multi_read_ex函数来读取所有IO的状态。然后,将这十六位的数值与读取的IO数值进行比较。如果数值为0,则输出为低电平;反之,则为高电平。


       14.2.2 C模块构造与类的方法


       1,atk_xl9555类与IIC构造函数

       xl9555和IIC的构造对象方法如下:

class machine.I2C(id, scl, sda, freq=400000)
class atk_xl9555.init(iic)
使用示例:
i2c0 = I2C(0, scl = Pin(42), sda = Pin(41), freq = 400000)
xl9555 = atk_xl9555.init(i2c0)

       该构造方法的参数描述,如下表所示。


表14.2.2.1 IIC和XL9555构造函数参数描述


       返回值:IIC和XL9555对象。


       2,xl9555类的方法


       ①:控制某个扩展IO输出电平

       其方法原型如下:

xl9555.write_bit(pin, value)

        该函数的参数描述,如下表所示。


表14.2.2.2 xl9555.write_bit方法参数描述


       返回值:无。


       ②:读取某个扩展IO电平。

       其方法原型如下:

xl9555.read_bit(pin)

        该方法的参数描述,如下表所示。


表14.2.3.3 xl9555.write_bit方法参数描述


       返回值:读取IO的电平值


       ③:按键扫描。

       其方法原型如下:

xl9555.key_scan()

        返回值:按下的按键值。


        14.3 硬件设计


       1. 例程功能

       本章实验功能简介:通过按下KEY0~4按键来控制蜂鸣器和LED灯开关状态,KEY0和KEY1控制蜂鸣器开与关;KEY2和KEY3控制LED灯开与关。


       2. 硬件资源


       1)LED灯

              LED-IO1


       2)XL9555

              IIC_INT-IO0(需在P5连接IO0)

              IIC_SDA-IO41

              IIC_SCL-IO42

              IO_17-KEY0

              IO_16-KEY1

              IO_15-KEY2

              IO_14-KEY3


       3. 原理图

       XL9555、独立按键和蜂鸣器硬件部分的原理图,如下图所示。


图14.3.1 XL9555、独立按键和蜂鸣器原理图


       这里需要注意的是:独立按键设计为采样到按键另一端的低电平为有效电平。


        14.4 软件设计


       14.4.1 程序流程图

       程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图:


图14.4.1.1 程序流程图


       14.4.2 程序解析

       本书籍的代码都在main.py脚本下编写的,读者可在光盘资料下找到对应的源码。IO扩展实验main.py源码如下:

from machine import Pin,I2C
import atk_xl9555 as io_ex
import time
 
 
"""
 * @brief       程序入口
 * @param       无
 * @retval      无
"""
if __name__ == '__main__':
    
    # 初始化LED并输出高电平
    led = Pin(1,Pin.OUT,value = 1)
    # IIC初始化
    i2c0 = I2C(0, scl = Pin(42), sda = Pin(41), freq = 400000)
    # XL9555初始化
    xl9555 = io_ex.init(i2c0)
    
    while True:
        
        # 获取按键值
        key = int(xl9555.key_scan())
        
        if key == io_ex.KEY0:
            xl9555.write_bit(io_ex.BEEP,0)      # 打开蜂鸣器
        elif key == io_ex.KEY1:
            xl9555.write_bit(io_ex.BEEP,1)      # 关闭蜂鸣器
        elif key == io_ex.KEY2:
            led.value(0)                        # 打开LED
        elif key == io_ex.KEY3:
            led.value(1) 
 
        time.sleep_ms(10)                       # 延时10ms

       这示例代码的功能是:使用I2C通信协议,通过操作XL9555芯片来控制LED灯和蜂鸣器的状态。主程序进入无限循环,不断地扫描XL9555芯片的按键输入。如果KEY0被按下,则打开蜂鸣器;如果KEY1被按下,则关闭蜂鸣器;如果KEY2被按下,则关闭LED灯;如果KEY3被按下,则打开LED灯。每次按键操作后,程序会暂停10毫秒,以等待下一次按键输入。


        14.5 下载验证

       下载代码完成后,按键被按下时,程序会执行特定的操作。例如,如果按下KEY0,程序会启动蜂鸣器;如果按下KEY1,程序会关闭蜂鸣器;如果按下KEY2,程序会关闭LED灯;如果按下KEY3,程序会打开LED灯。


请使用浏览器的分享功能分享到微信等