[MM32生态] 基于MM32的手持终端设备(国产芯片替代方案)

芯片在我们生活中无处不在,常用的手机、汽车、家用电器、智能家居、消费电子等等都离不开芯片,以及上班途中骑行的共享单车,也都有芯片身影的存在。在当前市场大环境下,尤其是之前使用国外芯片(ST、NXP等)方案设计的项目产品,缺芯的现象尤为严重,对于量小的客户而言,高价芯片、一芯难求的现象比比皆是;而对于国产芯片的发力,芯片国产化、推动国产芯片完成实际项目落地的需求,也成了当前国内设计项目之初作为芯片选型的主要依据;而对于已经完成的项目,在缺芯的因素下使用国产芯片替换原有芯片的需求量也变得日益增多;国产芯片不但解决了芯片短缺的问题,同时在价格成本上具有很大的优势,随着国产芯片技术逐步趋向稳定、成熟,并且在以飞快的速度创新着,在做好项目前期功能验证的情况下,国产芯片完全可以做为选型首选品牌!

本文主要是分享使用国产芯片MM32F3273G8P去替换STM32F103VET6在手持式终端设备上的产品,主要是依据产品已经设计好的硬件原理图,去比较这两款单片机电气特性和引脚功能上的兼容性;对于软件代码来说,只需要上层的应用控制逻辑不需要改动,底层驱动根据芯片选型做相应的更新就可以了。当然也有遇到过说是可以做到跟ST做到芯片PIN TO PIN,软件不需要改动的国产芯片,但使用ST的软件代码库运行在非ST的芯片上,终究不是原配,出了问题是芯片的问题,还是软件库的问题,也无法确认;做项目还是稳妥些好,不要随便的拿来主义,要经过充分验证和测试,才能保证产品的稳定性不是吗?

因为是实际的产品项目,不可能直接分享实际的功能实现,但会结合硬件设计来做一个相近的软件功能出来展示,来体现这两款芯片的硬件和软件的兼容性。这款手持终端设备在硬件设计上使用STM32F103VET6作为主控芯片、锂电池供电,并带有充电管理电路、具有一键开关机和一键复位功能、带有2.8寸的TFT彩色LCD液晶显示屏,并伴有背光控制电路、带有USB接口电路,一方面给整机充电,另一方面可以与PC通过USB接口进行数据通讯、带有外置的RTC芯片,实现精确时间管理功能、支持2片SPI FLASH,可提供大容量的离线数据存储和管理、通过TM1668芯片驱动实现一个外置的4*5按键矩阵检测功能、带有1路蜂鸣器,供操作时当作提示音、此外还有FM17522实现的非接触式IC卡控制电路和FM11NC08的NFC TAG电路,以及LTK4201移动支付加密芯片电路等。


 

芯片规格

MM32F3273G8P芯片与STM32F103VET6芯片相比,在SRAM大小、UART数量、GPIO口个数、以及CPU频率和工作电压范围上有明显的优势,但在ADC单元数上有些欠缺;当然这只是规格比较的结果,还需要结合实际的硬件设计电路来看;就比如ADC欠缺的部分,在这个手持终端设备上就只用了2路,完全满足硬件需求,芯片就可以直接替换使用了。

芯片引脚定义

MM32F3272G8P和STM32F103VET6都是LQFP100的封装结合,在引脚定义上是PIN TO PIN兼容的,在引脚功能上也是相匹配的,具体可以参考这两款芯片数据手册的相关章节;如下图所示:

基于MM32F3273G8P的软件功能实现

基于手持设备终端,通过按键、显示、非接触式IC卡的读写操作,模拟完成客户在消费时的消费、充值、查询、设置等功能,结合硬件设计,驱动部分的功能如下所示:

  • 通过FSMC接口驱动显示LCD显示,通过GPIOLCD背光进行控制;
  • 通过硬件I2C接口驱动PCF8563 RTC芯片,进行精确时钟管理,并在LCD液晶屏上显示当前的日期和时间信息;
  • 通过GPIO口模拟TM1668的控制时序,实现对4*5按键矩阵的检测和识别,对检测到的按键在不同状态任务下做相应的功能处理;
  • 通过硬件SPI接口驱动2SPI FLASH,实现离线数据存储和管理的功能,其中SPI FLASH1部分空间用作中文汉字字库点阵数据的存储空间使用;
  • 通过ADC检测当前锂电池电量和供电电压;
  • 通过TIM定时器的PWM功能,实现对蜂鸣器的驱动,完成操作提示音的功能;
  • 通过硬件SPI接口驱动FM17522芯片驱动,实现对非接触式IC卡的读写操作;
  • 通过Xmodem串行文件传输协议实现中文汉字字库点阵数据烧录到SPI FLASH的功能;

软件分层/模块设计

整体软件分为MM32库、SHELL调试、SYS系统调度、NFC层、HAL层、BSP层、以及APP层;其中MM32库是灵动官方提供的库程序;SHELL调试是基于UART实现的,用于项目设计过程中的调试等功能分析;SYS系统调度包含了消息队列及TASK任务的注册、轮询和调用;NFC层实现了FM11NC08和FM17550的驱动及应用功能;HAL层实现了FSM有限状态机的控制、HMI的界面显示、KEY的按键处理、NVM的存储控制、以及RTC的应用;BSP层则是基于硬件原理图设计,实现了基于底层硬件的驱动程序,如ADC、BEEP、LCD、LKT4201、PCF8563、TIM1668、W25Q64等等;APP层是整体框架和Xmodem功能;具体的实现和代码可以参加附件中的软件工程源代码。

部分程序源码

LCDFSMC初始化及配置

void LCD_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;

    FSMC_InitTypeDef FSMC_InitStructure;

    FSMC_NORSRAM_Bank_InitTypeDef FSMC_BankInitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOC, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8 | GPIO_Pin_9;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;

    GPIO_Init(GPIOC, &GPIO_InitStructure);

    LCD_BL_L(); /* Turn OFF LCD Backlight */

    RCC_AHBPeriphClockCmd( RCC_AHBENR_GPIOD,   ENABLE);

    RCC_AHBPeriphClockCmd( RCC_AHBENR_GPIOE,   ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2ENR_SYSCFG, ENABLE);

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource4,  GPIO_AF_12);  /* FSMC_NOE  */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource5,  GPIO_AF_12);  /* FSMC_NWE  */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource7,  GPIO_AF_12);  /* FSMC_NE1  */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource11, GPIO_AF_12);  /* FSMC_A16  */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_12);  /* FSMC_DA0  */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_12);  /* FSMC_DA1  */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource0,  GPIO_AF_12);  /* FSMC_DA2  */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource1,  GPIO_AF_12);  /* FSMC_DA3  */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource7,  GPIO_AF_12);  /* FSMC_DA4  */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource8,  GPIO_AF_12);  /* FSMC_DA5  */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource9,  GPIO_AF_12);  /* FSMC_DA6  */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource10, GPIO_AF_12);  /* FSMC_DA7  */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource11, GPIO_AF_12);  /* FSMC_DA8  */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource12, GPIO_AF_12);  /* FSMC_DA9  */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource13, GPIO_AF_12);  /* FSMC_DA10 */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource14, GPIO_AF_12);  /* FSMC_DA11 */

    GPIO_PinAFConfig(GPIOE, GPIO_PinSource15, GPIO_AF_12);  /* FSMC_DA12 */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource8,  GPIO_AF_12);  /* FSMC_DA13 */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource9,  GPIO_AF_12);  /* FSMC_DA14 */

    GPIO_PinAFConfig(GPIOD, GPIO_PinSource10, GPIO_AF_12);  /* FSMC_DA15 */

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4 | GPIO_Pin_5  | GPIO_Pin_7  |

                                    GPIO_Pin_11;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;

    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1  | GPIO_Pin_8  |

                                    GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 |

                                    GPIO_Pin_15;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;

    GPIO_Init(GPIOD, &GPIO_InitStructure);

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_7  | GPIO_Pin_8  | GPIO_Pin_9  |

                                    GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 |

                                    GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;

    GPIO_Init(GPIOE, &GPIO_InitStructure);

    RCC_AHB3PeriphClockCmd(RCC_AHB3ENR_FSMC, ENABLE);

    FSMC_NORSRAM_BankStructInit(&FSMC_BankInitStructure);

    FSMC_BankInitStructure.FSMC_SMReadPipe    = 0;

    FSMC_BankInitStructure.FSMC_ReadyMode     = 0;

    FSMC_BankInitStructure.FSMC_WritePeriod   = 0x2;

    FSMC_BankInitStructure.FSMC_WriteHoldTime = 2;

    FSMC_BankInitStructure.FSMC_AddrSetTime   = 3;

    FSMC_BankInitStructure.FSMC_ReadPeriod    = 0x1;

    FSMC_BankInitStructure.FSMC_DataWidth     = FSMC_DataWidth_16bits;

    FSMC_NORSRAM_Bank_Init(&FSMC_BankInitStructure, FSMC_NORSRAM_BANK0);

    FSMC_NORSRAMStructInit(&FSMC_InitStructure);

    FSMC_InitStructure.FSMC_Mode            = FSMC_Mode_8080;

    FSMC_InitStructure.FSMC_TimingRegSelect = FSMC_TimingRegSelect_0;

    FSMC_InitStructure.FSMC_MemSize         = FSMC_MemSize_64MB;

    FSMC_InitStructure.FSMC_MemType         = FSMC_MemType_NorSRAM;

    FSMC_InitStructure.FSMC_AddrDataMode    = FSMC_AddrDataMUX;

    FSMC_NORSRAMInit(&FSMC_InitStructure);

    LCD_RST_H(); SysTick_DelayMS(100);

    LCD_RST_L(); SysTick_DelayMS(100);

    LCD_RST_H(); SysTick_DelayMS(200);

    LCD_REG_Configure();

}

PCF8563 RTC芯片驱动程序

void PCF8563_Read(uint8_t Address, uint8_t *Buffer, uint8_t Length)

{

    uint8_t i = 0, Flag = 0, Count = 0;

    I2C_SendData(I2C1, Address);

    while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));

    for(i = 0; i < Length; i++)

    {

        while(1)

        {

            if((I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFNF)) && (Flag == 0))

            {

                I2C_ReadCmd(I2C1);   Count++;

                if(Count == Length) Flag = 1;

            }

            if(I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_RFNE))

            {

                Buffer[i] = I2C_ReceiveData(I2C1);     break;

            }

        }

    }

    I2C_GenerateSTOP(I2C1, ENABLE);

    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_STOP_DET));

}

void PCF8563_Write(uint8_t Address, uint8_t *Buffer, uint8_t Length)

{

    uint8_t i = 0;

    I2C_SendData(I2C1, Address);

    while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));

    for(i = 0; i < Length; i++)

    {

        I2C_SendData(I2C1, Buffer[i]);

        while(!I2C_GetFlagStatus(I2C1, I2C_STATUS_FLAG_TFE));

    }

    I2C_GenerateSTOP(I2C1, ENABLE);

    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_STOP_DET));

}

void PCF8563_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;

    I2C_InitTypeDef  I2C_InitStructure;

    uint8_t wBuffer[7] = {0x00, 0x56, 0x12, 0x29, 0x03, 0x12, 0x21};

    uint8_t rBuffer[7] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    RCC_APB1PeriphClockCmd(RCC_APB1ENR_I2C1, ENABLE);

    I2C_StructInit(&I2C_InitStructure);

    I2C_InitStructure.I2C_Mode       = I2C_Mode_MASTER;

    I2C_InitStructure.I2C_OwnAddress = 0;

    I2C_InitStructure.I2C_Speed      = I2C_Speed_STANDARD;

    I2C_InitStructure.I2C_ClockSpeed = 100000;

    I2C_Init(I2C1, &I2C_InitStructure);

    I2C_Send7bitAddress(I2C1, PCF8563_I2C_ADDRESS, I2C_Direction_Transmitter);

    I2C_Cmd(I2C1, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_4);

    GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_4);

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8 | GPIO_Pin_9;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_OD;

    GPIO_Init(GPIOB, &GPIO_InitStructure);

    PCF8563_Read(0x02, rBuffer, sizeof(rBuffer));

    if(rBuffer[6] != 0x21)

    {

        printf("\r\nRTC Factory Reset!!!\r\n");

        PCF8563_Write(0x02, wBuffer, sizeof(wBuffer));

    }

}

TM1668按键识别驱动程序

uint8_t TM1668_RD(void)

{

    uint8_t Data = 0;

    TM1668_InitDIO(0);  TM1668_DIO_L();

    TM1668_InitDIO(1);

    TM1668_CLK_L();     TM1688_Delay();

    for(uint8_t i = 0; i < 8; i++)

    {

        Data >>=  1;

        TM1668_CLK_H(); TM1688_Delay();

        if(GPIO_ReadInputDataBit(TM1668_DIO_GPIO, TM1668_DIO_PIN) == Bit_SET)

        {

            Data |= 0x80;

        }

        TM1668_CLK_L(); TM1688_Delay();

    }

    return Data;

}

void TM1668_WR(uint8_t Data)

{

    TM1668_InitDIO(0);  TM1668_DIO_L();

    for(uint8_t i = 0; i < 8; i++)

    {

        TM1668_CLK_L(); TM1688_Delay();

        if(Data & (0x01 << i)) TM1668_DIO_H();

        else                   TM1668_DIO_L();

        TM1668_CLK_H(); TM1688_Delay();

    }

}

void TM1668_Init(void)

{

    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(TM1668_DIO_RCC, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = TM1668_DIO_PIN;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;

    GPIO_Init(TM1668_DIO_GPIO, &GPIO_InitStructure);

    RCC_AHBPeriphClockCmd(TM1668_CLK_RCC, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = TM1668_CLK_PIN;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;

    GPIO_Init(TM1668_CLK_GPIO, &GPIO_InitStructure);

    RCC_AHBPeriphClockCmd(TM1668_STB_RCC, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin   = TM1668_STB_PIN;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;

    GPIO_Init(TM1668_STB_GPIO, &GPIO_InitStructure);

    TM1668_STB_H();

    TM1668_CLK_H();

    TM1668_DIO_L();

}

void TM1668_ReadInput(uint16_t *Buffer)

{

    TM1668_STB_L();

    TM1668_WR(0x42); TM1688_Delay();

    for(uint16_t i = 0; i < 5; i++)

    {

        Buffer[i] = TM1668_RD() + (0x0100 * i);

    }

    TM1668_STB_H();

}

FM17522非接触式IC卡操作

uint8_t FM17550_TypeA_Handler(void)

{

    FM17550_SoftwareReset();

    ReaderA_Init();

    FM17550_SetCW(3);

    if(ReaderA_CardActivate() != FM175XX_SUCCESS)

    {

        FM17550_SetCW(0);       return 0;

    }

    printf("\r\n-> ATQA = ");

    for(uint8_t i = 0; i < 2; i++)

    {

        printf("0x%02x ", PICC_A.ATQA[i]);

    }

    printf("\r\n-> UID  = ");

    for(uint8_t i = 0; i < 4; i++)

    {

        printf("0x%02x ", PICC_A.UID[i]);

    }

    printf("\r\n-> SAK  = 0x%02x", PICC_A.SAK);

    if(Mifare_Auth(KEY_A_AUTH, 0, KEY_A[0], PICC_A.UID) != FM175XX_SUCCESS)

    {

        printf("\r\n-> AUTH ERROR!\r\n");               

        FM17550_SetCW(0);            return 0;

    }

    printf("\r\n-> AUTH SUCCESS!");        

#if 0

    memcpy(BLOCK_VAULE,"\x00\x00\x00\x08",4);

               

    if(Mifare_SetBlock(1, BLOCK_VAULE) != FM175XX_SUCCESS)

    {

        printf("-> SET BLOCK VALUE ERROR!\r\n");               

        FM17550_SetCW(0);       return 0;

    }

    printf("-> SET BLOCK VALUE SUCCESS!\r\n");

#endif

    if(Mifare_ReadBlock(1, BLOCK_DATA) != FM175XX_SUCCESS)

    {

        printf("\r\n-> READ BLOCK ERROR!\r\n");               

        FM17550_SetCW(0);             return 0;

    }

    printf("\r\n-> READ BLOCK = ");

    for(uint8_t i = 0; i < 16; i++)

    {

        printf("0x%02x ", BLOCK_DATA[i]);

    }

    RemainingAmount  = BLOCK_DATA[0]; RemainingAmount *= 256;

    RemainingAmount += BLOCK_DATA[1]; RemainingAmount *= 256;

    RemainingAmount += BLOCK_DATA[2]; RemainingAmount *= 256;

    RemainingAmount += BLOCK_DATA[3];

    printf("\r\nRemainingAmount : %d", RemainingAmount);

    switch(AmountOperation)

    {

        case 1: /* 消费 */

            printf("\r\nConsumptionAmount : %d", ConsumptionAmount);

            if(RemainingAmount >= ConsumptionAmount)

            {

                BLOCK_VAULE[3] = (uint8_t)(((RemainingAmount - ConsumptionAmount) >> 0x00) & 0xFF);

                BLOCK_VAULE[2] = (uint8_t)(((RemainingAmount - ConsumptionAmount) >> 0x08) & 0xFF);

                BLOCK_VAULE[1] = (uint8_t)(((RemainingAmount - ConsumptionAmount) >> 0x10) & 0xFF);

                BLOCK_VAULE[0] = (uint8_t)(((RemainingAmount - ConsumptionAmount) >> 0x18) & 0xFF);

                if(Mifare_SetBlock(1, BLOCK_VAULE) != FM175XX_SUCCESS)

                {

                    printf("\r\n-> SET BLOCK VALUE ERROR!\r\n");               

                    FM17550_SetCW(0);                  return 0;

                }

                printf("\r\n-> SET BLOCK VALUE SUCCESS!");

            }

            else

            {

                printf("-> Amount Not Enough!\r\n");               

                FM17550_SetCW(0);          return 2;

            }

            break;

        case 2: /* 充值 */

            printf("\r\nRechargeAmount : %d", RechargeAmount);

            BLOCK_VAULE[3] = (uint8_t)(((RemainingAmount + RechargeAmount) >> 0x00) & 0xFF);

            BLOCK_VAULE[2] = (uint8_t)(((RemainingAmount + RechargeAmount) >> 0x08) & 0xFF);

            BLOCK_VAULE[1] = (uint8_t)(((RemainingAmount + RechargeAmount) >> 0x10) & 0xFF);

            BLOCK_VAULE[0] = (uint8_t)(((RemainingAmount + RechargeAmount) >> 0x18) & 0xFF);

            if(Mifare_SetBlock(1, BLOCK_VAULE) != FM175XX_SUCCESS)

            {

                printf("\r\n-> SET BLOCK VALUE ERROR!\r\n");               

                FM17550_SetCW(0);                  return 0;

            }

            printf("\r\n-> SET BLOCK VALUE SUCCESS!");

            break;

        case 3: /* 查询 */

            break;

        case 4: /* 清空 */

            memset(BLOCK_VAULE, 0, sizeof(BLOCK_VAULE));

            if(Mifare_SetBlock(1, BLOCK_VAULE) != FM175XX_SUCCESS)

            {

                printf("\r\n-> SET BLOCK VALUE ERROR!\r\n");               

                FM17550_SetCW(0);                  return 0;

            }

            printf("\r\n-> SET BLOCK VALUE SUCCESS!");

            break;

        default:

            break;

    }

    FM17550_SetCW(0);           return 1;

}

实际显示效果

主界面

消费操作界面

充值操作界面

查询操作界面

设置操作界面

关机界面

附件

演示程序源代码:  Project.zip (841.5 KB)


---------------------
作者:xld0932
链接:https://bbs.21ic.com/icview-3226582-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值