STM32F103,SPI------FLASH
一、SPI简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。是一种高速的、全双工、同步的通信总线,并且在芯片的管教上只占用4根线,节约了芯片的管脚,同时位PCB的布局上节省空间,提供方便,正是这种简单易用的特性,越来越多的芯片集成了这种通信协议,STM32也有SPI接口。
二、SPI应用
1、4线SPI:
(1)SCK/SCLK:同步时钟线(由主机产生发送给从机)
(2)CS:片选线(低电平有效)
(3)MISO:主机输入,从机输出(数据由从机发出)
(4)MOSI:主机输出,从机输入(数据由主机发出)
注:时钟信号是一个振荡信号,它告诉接收端在确切的时机对数据线上的信号进行采样
SPI是一个同步的数据总线,也就是说它是用单独的数据线和一个单独的时钟信号来保证发送端和接收端的同步
2、整体传输过程:
(1)主机先将CS片选线拉低,这样保证开始数据接收
(2)当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就能得到了一位数据(1bit)。
(3)主机发送到从机时:主机产生相应的时钟信号,然后数据一位位地从MOSI信号线上进行发送到从机
(4)主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续产生预定数量的时钟信号,并且从机会将数据通过MISO信号线发送给主机
注:SPI是全双工(具有单独的发送和接收线路),因此可以在同一时间发送和接收数据。
另外SPI的接收硬件可以是一个简单的移位寄存器。
3、SPI数据链路层
4、时钟相位和时钟极性
时钟极性(CPOL): 0 (SCK信号线在空闲时为低电平)规定开始时钟为低电平
1(SCK信号线在空闲时为 高电平)规定开始时钟为高电平
时钟相位(CPHA):用来决定何时进行信号采样,0在第一个跳变沿,1在第二个跳变沿,至于是上升沿还是下降沿则由CPOL相位极性来表示
如果CPHA=0,CPOL=1,则在SCK时钟的第一个边沿为下降沿时进行数据采样。
如果CPHA=0,CPOL=0,则在SCK时钟的第一个边沿为上升沿时进行数据采样。
如果CPHA=1,CPOL=1,则在SCK时钟的第二个边沿为下降沿时进行数据采样。
如果CPHA=1,CPOL=0,则在SCK时钟的第二个边沿为上升沿时进行数据采样。
5、SPI主要特点有:
可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
STM32的SPI功能很强大,SPI时钟最多可以到18Mhz,支持DMA,可以配置为SPI协议或者I2S协议
6、工作框图
STM32 SPI NSS大揭秘_andylauren的博客-CSDN博客 |
|||
SPI主设备(MSTR=1) |
硬件模式:SSM=0 |
输入模式:SSOE=0 |
在外部NSS引脚为高电平,即内部NSS引脚也为高电平时,才能进行数据传输。 如果要使能从设备,还需要一个GPIO引脚。 允许多主机模式 |
输出模式:SSOE=1 |
外部NSS引脚会输出低电平,使能从设备,进行数据传输。 不需要额外的GPIO引脚就能控制从设备。 |
||
软件模式:SSM=1 |
SSI=1,将内部NSS引脚设置为高电平。这样随时可以传输数据。当然多数情况还需要一个GPIO引脚输出低电平,来使能从设备,让从设备可以接收数据。 |
||
SPI从设备(MSTR=0) |
软件(SSM=1) |
并SSI=0.让内部NSS引脚为低电平,此时可以传送数据。 |
|
硬件(SSM=0) |
当外部NSS为低电平时,内部NSS也为低电平,此时可以传送数据 |
7、FLASH-------(W25Q64模块)
(1)W25Q64介绍:
W25Q64 (64M-bit),W25Q16(16M-bit)和 W25Q32(32M-bit)是为系统提供一个最小的空间、引脚 和功耗的存储器解决方案的串行 Flash 存储器。25Q 系列比普通的串行 Flash 存储器更灵活,性能 更优越。基于双倍/四倍的 SPI,它们能够可以立即完成提供数据给 RAM,包括存储声音、文本和数 据。芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于 5mA,掉电时低于 1uA。
W25Q64/16/32 由每页 256 字节组成。每页的 256 字节用一次页编程指令即可完成。每次可以擦 除 16 页(1 个扇区)、128 页(32KB 块)、256 页(64KB 块)和全片擦除。
存储空间:8M-----------128块/2048扇区
1块:16扇区 4096*16字节 即64kb 1kb=1024字节(b)
1扇区:16页 4096字节
1页:256字节
0x FF F F FF
块 扇区 页 字节
例如:0x05 6 3 102 (从而可以确定存储位置:第五块第六扇区第三页)
(2)
W25Q64/16/32 支持标准串行外围接口(SPI),和高速的双倍/四倍输出,双倍/四倍用的引脚: 串行时钟、片选端、串行数据 I/O0(DI)、I/O1(DO)、I/O2(WP)和 I/O3(HOLD)。SPI 最高支持 80MHz, 当用快读双倍/四倍指令时,相当于双倍输出时最高速率 160MHz,四倍输出时最高速率 320MHz。这 个传输速率比得上 8 位和 16 位的并行 Flash 存储器。
CS:CS为片选管脚,低电平有效。上电之后,在执行一条新命令之前,必须时CS引脚先有个低电平。
DO(MISO):DO为串行数据输出引脚,在CLK管脚的下降沿输出数据
WP:WP为写保护引脚,有效电平为低电平。高电平可读可写,低电平仅可读。
DI(MOSI):DI为串行数据输入引脚,数据、地址和命令从DI引脚输入到芯片内部,在CLK(串行时钟)管脚的上升沿捕获捕获数据。
CLK(SLCK):CLK为串行时钟引脚。SPI时钟引脚,为输入输出提供时钟脉冲
HOLD:HOLD为保持管脚,低电平有效。当CS为低电平,并且把HOLD拉低时,数据输出管脚将保持高阻态,并且会忽略数据输入管脚和时钟管脚上的信号。把HOLD管脚拉高,器件恢复正常工作。
(3)特点
●灵活的 4KB 扇区结构 SPI 串行存储器系列
-W25Q64:64M 位/8M 字节
-统一的扇区擦除(4K 字节) -W25Q16:16M 位/2M 字节
-块擦除(32K 和 64K 字节) -W25Q32:32M 位/4M 字节
-一次编程 256 字节 -每 256 字节可编程页
-至少 100,000 写/擦除周期
-数据保存 20 年
-每个设备具有唯一的 64 位 ID(1)
特点:Flash芯片内的数据只能由1变0,不能由0变1。
(4)W25Q64 应用SPI
W25Q64支持SPI数据传输时序模式0(CPOL = 0、CPHA = 0)和模式3(CPOL = 1、CPHA = 1),模式0和模式3主要区别是当SPI主机硬件接口处于空闲状态时,SCLK的电平状态是高电平或者是低电平。对于模式0来说,SCLK处于低电平;对于模式3来说,SCLK处于高电平。不过,在这两种模式下,芯片都是在SCLK的上升沿采集输入数据,下降沿输出数据。
W25Q64使用的数据传输格式:就是数据长度为8位大小,而且传输的时候,高位在前,低位在后
8、SPI应用
#include "spi.h"/*
Pb12 cs
pb13 sck
pb14 miso
pb15 mosi
*/
void Spi_Config(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//打开管脚时钟GPIO_InitTypeDef GPIO_InitStructure={0};//定义结构体 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15; //引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽模式GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(GPIOB, &GPIO_InitStructure);RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);SPI_InitTypeDef SPI_InitStructure = {0};
设置SPI 的通信方式,可以选择为半双工,全双工,以及串行发和串行收方式---这里选用全双工模式SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工模式SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主机模式SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8为的数据帧格式SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟极性设置---开始电平为低电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //时钟相位设置----第一个跳变沿SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件控制SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; /*就是设置 SPI 波特率预分频值----2分频这里选用*/SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位在前SPI_InitStructure.SPI_CRCPolynomial = 7; /* CRC 值计算的多项式 */SPI_Init(SPI2, &SPI_InitStructure);SPI_Cmd(SPI2,ENABLE);
}uint8_t Spi_SendData(uint8_t data)
{uint8_t rxdata=0;while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==0);SPI_I2S_SendData(SPI2,data);while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==0);rxdata = SPI_I2S_ReceiveData(SPI2);return rxdata;
}
#include "spi_flash.h"void sFLASH_Init(void)
{Spi_Config();}void sFLASH_EraseSector(uint32_t SectorAddr)
{/*!< Send write enable instruction */sFLASH_WriteEnable();sFLASH_CS_LOW();sFLASH_SendByte(sFLASH_CMD_SE);sFLASH_SendByte((SectorAddr & 0xFF0000) >> 16);sFLASH_SendByte((SectorAddr & 0xFF00) >> 8);sFLASH_SendByte(SectorAddr & 0xFF);sFLASH_CS_HIGH();/*!< Wait the end of Flash writing */sFLASH_WaitForWriteEnd();
}void sFLASH_EraseBulk(void)
{/*!< Send write enable instruction */sFLASH_WriteEnable();/*!< Bulk Erase *//*!< Select the FLASH: Chip Select low */sFLASH_CS_LOW();/*!< Send Bulk Erase instruction */sFLASH_SendByte(sFLASH_CMD_BE);/*!< Deselect the FLASH: Chip Select high */sFLASH_CS_HIGH();/*!< Wait the end of Flash writing */sFLASH_WaitForWriteEnd();
}void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{/*!< Enable the write access to the FLASH */sFLASH_WriteEnable();/*!< Select the FLASH: Chip Select low */sFLASH_CS_LOW();/*!< Send "Write to Memory " instruction */sFLASH_SendByte(sFLASH_CMD_WRITE);/*!< Send WriteAddr high nibble address byte to write to */sFLASH_SendByte((WriteAddr & 0xFF0000) >> 16);/*!< Send WriteAddr medium nibble address byte to write to */sFLASH_SendByte((WriteAddr & 0xFF00) >> 8);/*!< Send WriteAddr low nibble address byte to write to */sFLASH_SendByte(WriteAddr & 0xFF);/*!< while there is data to be written on the FLASH */while (NumByteToWrite--){/*!< Send the current byte */sFLASH_SendByte(*pBuffer);/*!< Point on the next byte to be written */pBuffer++;}/*!< Deselect the FLASH: Chip Select high */sFLASH_CS_HIGH();/*!< Wait the end of Flash writing */sFLASH_WaitForWriteEnd();
}void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;Addr = WriteAddr % sFLASH_SPI_PAGESIZE;count = sFLASH_SPI_PAGESIZE - Addr;NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;if (Addr == 0) /*!< WriteAddr is sFLASH_PAGESIZE aligned */{if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */{sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);}else /*!< NumByteToWrite > sFLASH_PAGESIZE */{while (NumOfPage--){sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);WriteAddr += sFLASH_SPI_PAGESIZE;pBuffer += sFLASH_SPI_PAGESIZE;}sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);}}else /*!< WriteAddr is not sFLASH_PAGESIZE aligned */{if (NumOfPage == 0) /*!< NumByteToWrite < sFLASH_PAGESIZE */{if (NumOfSingle > count) /*!< (NumByteToWrite + WriteAddr) > sFLASH_PAGESIZE */{temp = NumOfSingle - count;sFLASH_WritePage(pBuffer, WriteAddr, count);WriteAddr += count;pBuffer += count;sFLASH_WritePage(pBuffer, WriteAddr, temp);}else{sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);}}else /*!< NumByteToWrite > sFLASH_PAGESIZE */{NumByteToWrite -= count;NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;sFLASH_WritePage(pBuffer, WriteAddr, count);WriteAddr += count;pBuffer += count;while (NumOfPage--){sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);WriteAddr += sFLASH_SPI_PAGESIZE;pBuffer += sFLASH_SPI_PAGESIZE;}if (NumOfSingle != 0){sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);}}}
}void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{/*!< Select the FLASH: Chip Select low */sFLASH_CS_LOW();/*!< Send "Read from Memory " instruction */sFLASH_SendByte(sFLASH_CMD_READ);/*!< Send ReadAddr high nibble address byte to read from */sFLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/*!< Send ReadAddr medium nibble address byte to read from */sFLASH_SendByte((ReadAddr& 0xFF00) >> 8);/*!< Send ReadAddr low nibble address byte to read from */sFLASH_SendByte(ReadAddr & 0xFF);while (NumByteToRead--) /*!< while there is data to be read */{/*!< Read a byte from the FLASH */*pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);/*!< Point to the next location where the byte read will be saved */pBuffer++;}/*!< Deselect the FLASH: Chip Select high */sFLASH_CS_HIGH();
}uint32_t sFLASH_ReadID(void)
{uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;/*!< Select the FLASH: Chip Select low */sFLASH_CS_LOW();/*!< Send "RDID " instruction */sFLASH_SendByte(0x9F);/*!< Read a byte from the FLASH */Temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);/*!< Read a byte from the FLASH */Temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);/*!< Read a byte from the FLASH */Temp2 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);/*!< Deselect the FLASH: Chip Select high */sFLASH_CS_HIGH();Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;return Temp;
}void sFLASH_StartReadSequence(uint32_t ReadAddr)
{/*!< Select the FLASH: Chip Select low */sFLASH_CS_LOW();/*!< Send "Read from Memory " instruction */sFLASH_SendByte(sFLASH_CMD_READ);/*!< Send the 24-bit address of the address to read from -------------------*//*!< Send ReadAddr high nibble address byte */sFLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/*!< Send ReadAddr medium nibble address byte */sFLASH_SendByte((ReadAddr& 0xFF00) >> 8);/*!< Send ReadAddr low nibble address byte */sFLASH_SendByte(ReadAddr & 0xFF);
}/*** @brief Reads a byte from the SPI Flash.* @note This function must be used only if the Start_Read_Sequence function* has been previously called.* @param None* @retval Byte Read from the SPI Flash.*/
uint8_t sFLASH_ReadByte(void)
{return (sFLASH_SendByte(sFLASH_DUMMY_BYTE));
}uint8_t sFLASH_SendByte(uint8_t byte)
{/*!< Loop while DR register in not emplty */while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_TXE) == RESET);/*!< Send byte through the SPI1 peripheral */SPI_I2S_SendData(sFLASH_SPI, byte);/*!< Wait to receive a byte */while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET);/*!< Return the byte read from the SPI bus */return SPI_I2S_ReceiveData(sFLASH_SPI);
}/*** @brief Sends a Half Word through the SPI interface and return the Half Word* received from the SPI bus.* @param HalfWord: Half Word to send.* @retval The value of the received Half Word.*/
uint16_t sFLASH_SendHalfWord(uint16_t HalfWord)
{/*!< Loop while DR register in not emplty */while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_TXE) == RESET);/*!< Send Half Word through the sFLASH peripheral */SPI_I2S_SendData(sFLASH_SPI, HalfWord);/*!< Wait to receive a Half Word */while (SPI_I2S_GetFlagStatus(sFLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET);/*!< Return the Half Word read from the SPI bus */return SPI_I2S_ReceiveData(sFLASH_SPI);
}/*** @brief Enables the write access to the FLASH.* @param None* @retval None*/
void sFLASH_WriteEnable(void)
{/*!< Select the FLASH: Chip Select low */sFLASH_CS_LOW();/*!< Send "Write Enable" instruction */sFLASH_SendByte(sFLASH_CMD_WREN);/*!< Deselect the FLASH: Chip Select high */sFLASH_CS_HIGH();
}/*** @brief Polls the status of the Write In Progress (WIP) flag in the FLASH's* status register and loop until write opertaion has completed.* @param None* @retval None*/
void sFLASH_WaitForWriteEnd(void)
{uint8_t flashstatus = 0;/*!< Select the FLASH: Chip Select low */sFLASH_CS_LOW();/*!< Send "Read Status Register" instruction */sFLASH_SendByte(sFLASH_CMD_RDSR);/*!< Loop as long as the memory is busy with a write cycle */do{/*!< Send a dummy byte to generate the clock needed by the FLASHand put the value of the status register in FLASH_Status variable */flashstatus = sFLASH_SendByte(sFLASH_DUMMY_BYTE);}while ((flashstatus & sFLASH_WIP_FLAG) == SET); /* Write in progress *//*!< Deselect the FLASH: Chip Select high */sFLASH_CS_HIGH();
}/*** @}*//*** @}*//*** @}*//*** @}*//*** @}*/ /******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/
STM32F103,SPI------FLASH相关推荐
- Nand Flash,Nor Flash,BPI Flash,SPI Flash 的区别?
转载:CFI Flash, JEDEC Flash ,Parellel Flash, SPI Flash, Nand Flash,Nor Flash的区别和联系 简单说就是,Flash,按照内部访问接 ...
- CFI Flash, SPI Flash, Nand Flash,Nor Flash的区别和联系
flash按照内部访问接口(技术)不同,flash分为两种:nor flash和nand flash. nor flash:像访问SDRAM一样,按照数据/地址总线直接访问:读数据快,写数据慢: na ...
- 基于STM32F103,SPI驱动GC9306屏幕
下面我将代码放在下面仅供参考,本人实测过哟,希望对你有用. 因为是驱动屏幕,所以是三线spi. gc9306.c文件 #include "gc9306.H" #include &q ...
- esp8266,esp32中的SPI FLASH 访问模式(QIO QOUT DIO DOUT)
本文 ESP8266 和 ESP32 支持四种不同的 SPI flash 访问模式:DIO.DOUT.QIO 和 QOUT. 这些可以通过 esptool.py write_flash 的 --fla ...
- STM32CubeMX学习笔记(25)——FatFs文件系统使用(操作SPI Flash)
一.FatFs简介 FatFs 是面向小型嵌入式系统的一种通用的 FAT 文件系统.它完全是由 ANSI C 语言编写并且完全独立于底层的 I/O 介质.因此它可以很容易地不加修改地移植到其他的处理器 ...
- Winbond W25QXX SPI Flash使用笔记
相较于EEPROM而言,SPI Flash的存储空间简直就是打开了一个新世界.以W25Q16为例,16Mb也就是2MB的空间,是AT24C08芯片的1KB空间的2048倍,价格也没有相差很多.同时使用 ...
- SPI Flash/Nor Flash/Nand Flash
目前嵌入式系统中常用的Flash有Nor和Nand两种,SPI Flash是两者中的一种,只是对外接口使用SPI串行接口而已.SPI Flash默认情况下我们指定的是Nor Flash.早起的Nor ...
- 搞清楚nand flash和 nor flash 以及 spi flash 和cfi flash 的区别
前言: 在嵌入式开发中,如uboot的移植,kernel的移植都需要对Flash 有基本的了解.下面细说一下标题中的中Flash中的关系. Flash Memory(闪存)是非易失性的存储器. ...
- 【STM32H7教程】第85章 STM32H7的SPI 总线应用之SPI Flash的STM32CubeProg下载算法制作
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第85章 STM32H7的SPI 总线应用之SPI ...
- 【理论】SPI Flash和E2PROM区别
SPI Flash和E2PROM两款存储芯片作为单片机常用的外部存储器件,其区别还是要去仔细把握一下的. 以W25Q128为代表的SPI Flash和以AT24C02为代表的E2PROM为例,下面我就 ...
最新文章
- 理解什么是MyBatis?
- windows dos 常用命令
- MarkDown页面添加锚点,跳转到本页指定位置
- Visual Studio listView控件绑定SQL Server数据库并动态显示数据,调整列宽
- hdata datax交流总结
- 数据结构基础(16) --树与二叉树
- 【Flink】Flink1.11.2 on YARN滚动日志配置
- Spring-tx-TransactionInfo
- oracle patch下载地址
- 5个CSS3技术实现设计增强
- iOS:const的使用
- 计算机软件文档编制规范百度云,计算机软件文档编制规范
- 中级微观经济学:Chap 15 市场需求
- Flutter 底部导航栏实现方式
- 为 Form Library 开发工作流,如何读取 InfoPath 表单内容
- chrome打开链接隐私设置错误_Chrome 隐私设置错误
- 快速翻译PDF文档的免费方法
- 包你笑,笑话之三 超强情侣对话
- Python super( ) 函数详解
- openCV实战项目--人脸考勤