发新帖本帖赏金 100.00元(功能说明)我要提问
12下一页
返回列表
打印
[STM32F4]

公司“砍掉”FIFO和晶振预算怎么办?教你这样巧开发STM32...

[复制链接]
5096|27
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
#申请原创# @21小跑堂
在之前的公司做过一个被压缩成本的0V7670无FIFO无晶振的拍照项目,主控使用STM32F407,确实节约了成本,但是没有FIFO确实很麻烦,因为FIFO可以暂存图像数据,有这颗芯片可以降低单片机对高速IO的限制,还节省CPU资源,但是没有也只能搞下去。
接线:
说起OV7670必须说接线,特别是阉割版本的模块,不然真的会头大,在接线这一步自己就因为接错,导致我耽误了很长时间。
因为没有FIFO,所以这里使用F407的DCMI接口。
因为是测试,所以直接用杜邦线连接,这个线简直头疼,太多太乱。这里给一下我的接线表和我的实物图。这里已经是能再LCD上面显示图片了。

另外还有两个接口,一个是POWER DOWN控制信号(PG9),一个是复位控制信号(PG15)。

PWDN    |    输入         |    POWER DOWN模式选择           0:工作               1:POWER DOWN

RESET   |   输入         |    初始化所有寄存器到默认值         0:RESET 模式  1:一般模式


外设初始化:
用到摄像头,还要拍照,这一快屏幕是一定少不了的,先从初始化LCD屏幕开始。
//初始化lcd
//该初始化函数可以初始化各种ILI93XX液晶,但是其他函数是基于ILI9320的!!!
//在其他型号的驱动芯片上没有测试!
void LCD_Init(void)
{         
        vu32 i=0;
        
  GPIO_InitTypeDef  GPIO_InitStructure;
        FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
  FSMC_NORSRAMTimingInitTypeDef  readWriteTiming;
        FSMC_NORSRAMTimingInitTypeDef  writeTiming;
        
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOD|RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF|RCC_AHB1Periph_GPIOG, ENABLE);//使能PD,PE,PF,PG时钟  
  RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE);//使能FSMC时钟  
        

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PB15 推挽输出,控制背光
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 //PB15 推挽输出,控制背光
        
  GPIO_InitStructure.GPIO_Pin = (3<<0)|(3<<4)|(7<<8)|(3<<14);//PD0,1,4,5,8,9,10,14,15 AF OUT
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化  
        
  GPIO_InitStructure.GPIO_Pin = (0X1FF<<7);//PE7~15,AF OUT
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化  

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化  

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC);//PD0,AF12
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource1,GPIO_AF_FSMC);//PD1,AF12
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource4,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource5,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource8,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource9,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource10,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource14,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_FSMC);//PD15,AF12

  GPIO_PinAFConfig(GPIOE,GPIO_PinSource7,GPIO_AF_FSMC);//PE7,AF12
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource8,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource10,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource12,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource13,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource14,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource15,GPIO_AF_FSMC);//PE15,AF12

  GPIO_PinAFConfig(GPIOF,GPIO_PinSource12,GPIO_AF_FSMC);//PF12,AF12
  GPIO_PinAFConfig(GPIOG,GPIO_PinSource12,GPIO_AF_FSMC);


  readWriteTiming.FSMC_AddressSetupTime = 0XF;         //地址建立时间(ADDSET)为16个HCLK 1/168M=6ns*16=96ns        
  readWriteTiming.FSMC_AddressHoldTime = 0x00;         //地址保持时间(ADDHLD)模式A未用到        
  readWriteTiming.FSMC_DataSetupTime = 60;                        //数据保存时间为60个HCLK        =6*60=360ns
  readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
  readWriteTiming.FSMC_CLKDivision = 0x00;
  readWriteTiming.FSMC_DataLatency = 0x00;
  readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;         //模式A
   

        writeTiming.FSMC_AddressSetupTime =9;              //地址建立时间(ADDSET)为9个HCLK =54ns
  writeTiming.FSMC_AddressHoldTime = 0x00;         //地址保持时间(A               
  writeTiming.FSMC_DataSetupTime = 8;                 //数据保存时间为6ns*9个HCLK=54ns
  writeTiming.FSMC_BusTurnAroundDuration = 0x00;
  writeTiming.FSMC_CLKDivision = 0x00;
  writeTiming.FSMC_DataLatency = 0x00;
  writeTiming.FSMC_AccessMode = FSMC_AccessMode_A;         //模式A


  FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;//  这里我们使用NE4 ,也就对应BTCR[6],[7]。
  FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 不复用数据地址
  FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM;  //SRAM   
  FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit   
  FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable;
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
        FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
  FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;   
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;  
  FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;        //  存储器写使能
  FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;   
  FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable; // 读写使用不同的时序
  FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
  FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming; //读写时序
  FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &writeTiming;  //写时序

  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置

  FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  // 使能BANK1
               
         delay_ms(50); // delay 50 ms
         LCD_WriteReg(0x0000,0x0001);
        delay_ms(50); // delay 50 ms
          lcddev.id = LCD_ReadReg(0x0000);   
           if(lcddev.id<0XFF||lcddev.id==0XFFFF||lcddev.id==0X9300)//读到ID不正确,新增lcddev.id==0X9300判断,因为9341在未被复位的情况下会被读成9300
        {        
                 //尝试9341 ID的读取               
                LCD_WR_REG(0XD3);                                   
                lcddev.id=LCD_RD_DATA();        //dummy read         
                 lcddev.id=LCD_RD_DATA();        //读到0X00
                  lcddev.id=LCD_RD_DATA();           //读取93                                                                  
                 lcddev.id<<=8;
                lcddev.id|=LCD_RD_DATA();          //读取41                                       
                 if(lcddev.id!=0X9341)                //非9341,尝试是不是6804
                {        
                         LCD_WR_REG(0XBF);                                   
                        lcddev.id=LCD_RD_DATA();         //dummy read         
                         lcddev.id=LCD_RD_DATA();           //读回0X01                           
                         lcddev.id=LCD_RD_DATA();         //读回0XD0                                   
                          lcddev.id=LCD_RD_DATA();        //这里读回0X68
                        lcddev.id<<=8;
                          lcddev.id|=LCD_RD_DATA();        //这里读回0X04         
                        if(lcddev.id!=0X6804)                //也不是6804,尝试看看是不是NT35310
                        {
                                LCD_WR_REG(0XD4);                                   
                                lcddev.id=LCD_RD_DATA();//dummy read  
                                lcddev.id=LCD_RD_DATA();//读回0X01         
                                lcddev.id=LCD_RD_DATA();//读回0X53        
                                lcddev.id<<=8;         
                                lcddev.id|=LCD_RD_DATA();        //这里读回0X10         
                                if(lcddev.id!=0X5310)                //也不是NT35310,尝试看看是不是NT35510
                                {
                                        LCD_WR_REG(0XDA00);        
                                        lcddev.id=LCD_RD_DATA();                //读回0X00         
                                        LCD_WR_REG(0XDB00);        
                                        lcddev.id=LCD_RD_DATA();                //读回0X80
                                        lcddev.id<<=8;        
                                        LCD_WR_REG(0XDC00);        
                                        lcddev.id|=LCD_RD_DATA();                //读回0X00               
                                        if(lcddev.id==0x8000)lcddev.id=0x5510;//NT35510读回的ID是8000H,为方便区分,我们强制设置为5510
                                        if(lcddev.id!=0X5510)                        //也不是NT5510,尝试看看是不是SSD1963
                                        {
                                                LCD_WR_REG(0XA1);
                                                lcddev.id=LCD_RD_DATA();
                                                lcddev.id=LCD_RD_DATA();        //读回0X57
                                                lcddev.id<<=8;         
                                                lcddev.id|=LCD_RD_DATA();        //读回0X61        
                                                if(lcddev.id==0X5761)lcddev.id=0X1963;//SSD1963读回的ID是5761H,为方便区分,我们强制设置为1963
                                        }
                                }
                        }
                 }         
        }
        if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510||lcddev.id==0X1963)//如果是这几个IC,则设置WR时序为最快
        {
                //重新配置写时序控制寄存器的时序                                                                        
                FSMC_Bank1E->BWTR[6]&=~(0XF<<0);//地址建立时间(ADDSET)清零         
                FSMC_Bank1E->BWTR[6]&=~(0XF<<8);//数据保存时间清零
                FSMC_Bank1E->BWTR[6]|=3<<0;                //地址建立时间(ADDSET)为3个HCLK =18ns           
                FSMC_Bank1E->BWTR[6]|=2<<8;         //数据保存时间(DATAST)为6ns*3个HCLK=18ns
        }else if(lcddev.id==0X6804||lcddev.id==0XC505)        //6804/C505速度上不去,得降低
        {
                //重新配置写时序控制寄存器的时序                                                                        
                FSMC_Bank1E->BWTR[6]&=~(0XF<<0);//地址建立时间(ADDSET)清零         
                FSMC_Bank1E->BWTR[6]&=~(0XF<<8);//数据保存时间清零
                FSMC_Bank1E->BWTR[6]|=10<<0;        //地址建立时间(ADDSET)为10个HCLK =60ns           
                FSMC_Bank1E->BWTR[6]|=12<<8;         //数据保存时间(DATAST)为6ns*13个HCLK=78ns
        }
         printf(" LCD ID:%x\r\n",lcddev.id); //打印LCD ID   
这里参考了正点原子的LCD的初始化,我说贴上的代码没有具体型号的屏幕初始化,因为代码太长,会占用我的篇幅,在我的上面贴的代码最后是打印LCD ID,打印完成后会进行if()语句的判断,判断属于哪个型号的LCD屏幕,再进行初始化,大家有需要可以参考正点原子的初始化。在初始化完成后会对LCD进行清屏。因为使用摄像头会占用大量的内存资源,所有我在板子上贴了一片外部SARM芯片,同样需要对其进行初始化。此过程已放入LCD初始化中。
 LCD_Display_Dir(0);                //默认为竖屏
        LCD_LED=1;                                //点亮背光
        LCD_Clear(WHITE);
exfuns初始化:
在使用摄像头拍照结束后,会将.bmp文件存入内存卡中,所以这里需要初始化exfuns,为fatfs相关变量申请内存 。
u8 exfuns_init(void)
{
        u8 i;
        for(i=0;i<_VOLUMES;i++)
        {
                fs[i]=(FATFS*)mymalloc(SRAMIN,sizeof(FATFS));        //为磁盘i工作区申请内存        
                if(!fs[i])break;
        }
        file=(FIL*)mymalloc(SRAMIN,sizeof(FIL));                //为file申请内存
        ftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL));                //为ftemp申请内存
        fatbuf=(u8*)mymalloc(SRAMIN,512);                                //为fatbuf申请内存
        if(i==_VOLUMES&&file&&ftemp&&fatbuf)return 0;  //申请有一个失败,即失败.
        else return 1;        
}
OV7670初始化:
初始化OV7670主要是初始化POWER DOWN控制信号(PG9)和复位控制信号(PG15),以及SCCB的接口,该接口是串行摄像机控制总线协议的英文名简称,相当于一个简易的I2C协议,同时也会初始化QVGA的分辨率。
//初始化OV7670
//返回0:成功
//返回其他值:错误代码
u8 OV7670_Init(void)
{
u16 i=0;
u16 reg=0;
u8 temp=0;
//设置IO
GPIO_InitTypeDef GPIO_InitStructure;

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);
//GPIOG9,15初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_15;//PG9,15推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //推挽输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
OV7670_PWDN=0;        //POWER ON
delay_ms(10);
OV7670_RST=0;        //复位OV7670
delay_ms(10);
OV7670_RST=1;        //结束复位
SCCB_Init(); //初始化SCCB 的IO口        
SCCB_WR_Reg(0X12, 0x80);        //软复位OV7670
delay_ms(50);
LED0=0;
//初始化 OV7670,采用QVGA分辨率(320*240)
for(i=0;i<sizeof(ov7670_init_reg_tbl)/sizeof(ov7670_init_reg_tbl[0]);i++)
{
SCCB_WR_Reg(ov7670_init_reg_tbl[i][0],ov7670_init_reg_tbl[i][1]);
}
return 0x00; //ok
}

//初始化SCCB接口
void SCCB_Init(void)
{                                
  GPIO_InitTypeDef  GPIO_InitStructure;

  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIOD时钟
  //GPIOF9,F10初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;//PD6,7 推挽输出
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;  //PD6,7 推挽输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化

        GPIO_SetBits(GPIOD,GPIO_Pin_6|GPIO_Pin_7);
        SCCB_SDA_OUT();           
}        
DCMI配置:

这里是一个重点,因为我的OV7670不带FIFO和晶振,而7670的CMOS芯片的时钟可以高达24M,而单片机的IO速度不够,退而求其次,使用DCMI的接口,勉强让其跑起来,当然,如果是ARM9或者DSP图像处理芯片就另说了,人家内存大,带camera接口,但是价格也感人。
DCMI接口是一个同步并行接口,能够接收外部 8 位、 10 位、 12 位或 14 位 CMOS 摄像头模块发出的高速数据流。可支持不同的数据格式: YCbCr4:2:2/RGB565 逐行视频和压缩数据 (JPEG)。  

DCMI可以接收54M的数据流,高达14根数据线和一条像素时钟线PIXCLK,且像素时钟的极性可编程,可以自定义在上升沿还是下降沿捕捉数据。
//DCMI初始化
void My_DCMI_Init(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;

        
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOA B C E 时钟
        RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE);//使能DCMI时钟
  //PA4/6初始化设置
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_6;//PA4/6   复用功能输出
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
        
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_6;// PB6/7   复用功能输出
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
        
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_11;//PC6/7/8/9/11 复用功能输出
  GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化        

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;//PE5/6  复用功能输出
  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化        

        GPIO_PinAFConfig(GPIOA,GPIO_PinSource4,GPIO_AF_DCMI); //PA4,AF13  DCMI_HSYNC
        GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_DCMI); //PA6,AF13  DCMI_PCLK  
         GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_DCMI); //PB7,AF13  DCMI_VSYNC
         GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_DCMI); //PC6,AF13  DCMI_D0  
         GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_DCMI); //PC7,AF13  DCMI_D1
        GPIO_PinAFConfig(GPIOC,GPIO_PinSource8,GPIO_AF_DCMI); //PC8,AF13  DCMI_D2
        GPIO_PinAFConfig(GPIOC,GPIO_PinSource9,GPIO_AF_DCMI); //PC9,AF13  DCMI_D3
        GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_DCMI);//PC11,AF13 DCMI_D4
        GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_DCMI); //PB6,AF13  DCMI_D5
        GPIO_PinAFConfig(GPIOE,GPIO_PinSource5,GPIO_AF_DCMI); //PE5,AF13  DCMI_D6
        GPIO_PinAFConfig(GPIOE,GPIO_PinSource6,GPIO_AF_DCMI); //PE6,AF13  DCMI_D7

        
        DCMI_DeInit();//清除原来的设置


  DCMI_InitStructure.DCMI_CaptureMode=DCMI_CaptureMode_Continuous;//连续模式
        DCMI_InitStructure.DCMI_CaptureRate=DCMI_CaptureRate_All_Frame;//全帧捕获
        DCMI_InitStructure.DCMI_ExtendedDataMode= DCMI_ExtendedDataMode_8b;//8位数据格式  
        DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_Low;//HSYNC 低电平有效
        DCMI_InitStructure.DCMI_PCKPolarity= DCMI_PCKPolarity_Falling;//PCLK 上升沿有效
        DCMI_InitStructure.DCMI_SynchroMode= DCMI_SynchroMode_Hardware;//硬件同步HSYNC,VSYNC
        DCMI_InitStructure.DCMI_VSPolarity=DCMI_VSPolarity_High;//VSYNC 低电平有效
        DCMI_Init(&DCMI_InitStructure);
        printf("start DCMI_IT_FRAME\r\n");
        DCMI_ITConfig(DCMI_IT_FRAME,ENABLE);//开启帧中断
        //DCMI_ITConfig(DCMI_IT_LINE,ENABLE); //开启行中断
        //DCMI_ITConfig(DCMI_IT_VSYNC,ENABLE); //开启场中断        
        DCMI_Cmd(ENABLE);        //DCMI使能
        printf("ENABLE DCMI_IT_FRAME OK\r\n");
  NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级1
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级3
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、
        printf("My_DCMI_Init OK\r\n");
}
DCMI的初始化中我们使用连续模式的全帧捕捉,在中断中我们选择帧中断,方便获取一个满屏的图像数据。
DCMI获取到的数据会保存在32位的数据寄存器DCMI_DR中,之后我们便可以通过DMA进行传输,图像的缓冲由DMA进行管理,不是由DCMI接管。
DCMI的DMA配置:
void DCMI_DMA_Init(u32 DMA_Memory0BaseAddr,u32 DMA_Memory1BaseAddr,u16 DMA_BufferSize,u32 DMA_MemoryDataSize,u32 DMA_MemoryInc)
{
        DMA_InitTypeDef  DMA_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
        DMA_DeInit(DMA2_Stream1);//等待DMA2_Stream1
        while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE){}//等待DMA2_Stream1可配置
        
  /* 配置 DMA Stream */
  DMA_InitStructure.DMA_Channel = DMA_Channel_1;  //通道1 DCMI通道
  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&DCMI->DR;//外设地址为:DCMI->DR
  DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr;//DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//外设到存储器模式
  DMA_InitStructure.DMA_BufferSize = DMA_BufferSize;//数据传输量

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc;//存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据长度:32位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize;//存储器数据长度
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; //FIFO模式        
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;//使用全FIFO
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
  DMA_Init(DMA2_Stream1, &DMA_InitStructure);//初始化DMA Stream
               
        if(DMA_Memory1BaseAddr)
  {
                DMA_DoubleBufferModeCmd(DMA2_Stream1,ENABLE);//双缓冲模式
          DMA_MemoryTargetConfig(DMA2_Stream1,DMA_Memory1BaseAddr,DMA_Memory_1);//配置目标地址1
        }        
        DMA_ITConfig(DMA2_Stream1,DMA_IT_TC,ENABLE);//开启传输完成中断
        
        NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级0
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;                //子优先级0
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                        //IRQ通道使能
        NVIC_Init(&NVIC_InitStructure);        //根据指定的参数初始化VIC寄存器、        
}


开始工作:
在启动MCU后需要初始化相关配置。
  LCD_Init();                                        //LCD初始化  
        FSMC_SRAM_Init();                        //初始化外部SRAM.
        W25QXX_Init();                                //初始化W25Q128
        exfuns_init();                                //为fatfs相关变量申请内存  
//  
//        KEY_Init();                                        //按键初始化
//        SD_Init();
        //TIM3_Int_Init(10000-1,8400-1);//10Khz计数,1秒钟中断一次
        TIM1_PWM_Init();

        my_mem_init(SRAMIN);                //初始化内部内存池
        my_mem_init(SRAMEX);                //初始化内部内存池  
        my_mem_init(SRAMCCM);                //初始化CCM内存池
         usmart_dev.init(84);                //初始化USMART
         POINT_COLOR=RED;//设置字体为红色         
                LCD_ShowString(30,130,200,16,16,"OV7670 00089");
                printf("init ov\n");
        while(OV7670_Init())//初始化OV7670
        {
                LCD_ShowString(30,130,240,16,16,"OV7670 ERR");
                delay_ms(200);
          LCD_Fill(30,130,239,170,WHITE);
                delay_ms(200);
        }
        LCD_ShowString(30,130,200,16,16,"OV7670 OK");
        printf("init ov ok\n");
        delay_ms(1500);        

        jpeg_buf0=mymalloc(SRAMIN,jpeg_dma_bufsize*4);        //为jpeg dma接收申请内存        
        jpeg_buf1=mymalloc(SRAMIN,jpeg_dma_bufsize*4);        //为jpeg dma接收申请内存        
        jpeg_data_buf=mymalloc(SRAMEX,300*1024);                //为jpeg文件申请内存(最大300KB)
         pname=mymalloc(SRAMIN,30);//为带路径的文件名分配30个字节的内存         
         while(pname==NULL||!jpeg_data_buf)        //内存分配出错
         {            
                LCD_ShowString(30,190,240,16,16,"内存分配失败!");
                        printf("jpeg_data_buf ERROR!!!!!!!!!!!!!!!!!!!!!!! ok\n");
                delay_ms(200);                                 
                LCD_Fill(30,190,240,146,WHITE);//清除显示            
                delay_ms(200);                                 
        }
    printf("init mymalloc ok    \n");
        My_DCMI_Init();                        //DCMI配置
//        DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,10,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置  
//        DCMI_Start();                 //启动传输

        printf("init My_DCMI_Init ok\n");
        dcmi_rx_callback=jpeg_dcmi_rx_callback;//回调函数
        //DCMI_DMA_Init((u32)&LCD->LCD_RAM,0,10,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Disable);//DCMI DMA配置  
        DCMI_DMA_Init((u32)jpeg_buf0,(u32)jpeg_buf1,jpeg_dma_bufsize,DMA_MemoryDataSize_HalfWord,DMA_MemoryInc_Enable);//DCMI DMA配置(双缓冲模式)
        printf("init DCMI_Start");
        DCMI_Start();                         //启动传输
  OV7670_Window_Set(12,176,240,320);//OV7670设置输出窗口        
        //        sw_ov2640_mode();                //切换为OV2640模式
//while(1){
//        if(sd_ok==1){
        printf("start DCMI_Start ok ");
        dcmi=1;
        while(jpeg_data_ok!=1);        //等待第一帧图片采集完
        dcmi=0;
        DCMI_Stop(); //停止DMA搬运
        printf("DCMI STOP");
        
        sw_sdcard_mode();        //切换为SD卡模式
        printf("SWICCT SDCARD SUCCESS");
//                if(KEY0==0)        //BMP拍照
//                {
  f_mount(fs[0],"0:",1);                 //挂载SD卡  
        res=f_mkdir("0:/PHOTO");                //创建PHOTO文件夹
        if(res!=FR_EXIST&&res!=FR_OK)         //发生了错误
        {                 
                printf("SD CARD ERROR");
                LCD_ShowString(30,190,200,16,16,"内存分配失败!");
                delay_ms(200);                                 
                sd_ok=0;         
        }
        printf("SD CARD RIGHT");
        camera_new_pathname(pname,0);//得到文件名        
        printf("CREAT PNAME SUCCESS");
        res=bmp_encode(pname,0,0,lcddev.width,lcddev.height,0);
        printf("BMP SAVE SUCCESS");
                delay_ms(200);

此过程皆在main函数中完成,初始化DCMI之前必须先初始化内存并为jpeg dma接收申请和分配内存。
然后启动DMA,启动DCMI,进行图像采集。
//DCMI,启动传输
void DCMI_Start(void)
{
        LCD_Scan_Dir(U2D_L2R);                   //从上到下,从左到右        
        LCD_Set_Window(0,0,240,320); //LCD设置显示窗口,如果改变了分辨率,这里需要更改
        LCD_SetCursor(0,0);  
        LCD_WriteRAM_Prepare();                        //开始写入GRAM
        DMA_Cmd(DMA2_Stream1, ENABLE);//开启DMA2,Stream1
        DCMI_CaptureCmd(ENABLE);//DCMI捕获使能  
}
设置OV7670输出窗口        
//设置图像输出窗口
//对QVGA设置。
void OV7670_Window_Set(u16 sx,u16 sy,u16 width,u16 height)
{
        u16 endx;
        u16 endy;
        u8 temp;
        endx=sx+width*2;        //V
         endy=sy+height*2;
        if(endy>784)endy-=784;
        temp=SCCB_RD_Reg(0X03);                                //读取Vref之前的值
        temp&=0XF0;
        temp|=((endx&0X03)<<2)|(sx&0X03);
        SCCB_WR_Reg(0X03,temp);                                //设置Vref的start和end的最低2位
        SCCB_WR_Reg(0X19,sx>>2);                        //设置Vref的start高8位
        SCCB_WR_Reg(0X1A,endx>>2);                        //设置Vref的end的高8位

        temp=SCCB_RD_Reg(0X32);                                //读取Href之前的值
        temp&=0XC0;
        temp|=((endy&0X07)<<3)|(sy&0X07);
        SCCB_WR_Reg(0X32,temp);
        SCCB_WR_Reg(0X17,sy>>3);                        //设置Href的start高8位
        SCCB_WR_Reg(0X18,endy>>3);                        //设置Href的end的高8位
}
此时DCMI启动,会抓拍照片,当捕获到一帧完整数据会触发DCMI的帧中断,进入中断函数之后会进行JPEG的数据处理,在处理JPEG数据时会停止DMA的传输,并将数据保存到pbuf[]数组。
//DCMI中断服务函数
void DCMI_IRQHandler(void)
{
        if(DCMI_GetITStatus(DCMI_IT_FRAME)==SET)//捕获到一帧图像
        {
                //DCMI_Stop(); //停止DMA搬运

                DCMI_ClearITPendingBit(DCMI_IT_FRAME);//清除中断        
                if (dcmi == 1)
                        //printf("dcmi == 1 \r\n");                        
                        jpeg_data_process();
                //ov_frame++;
               
        }
        
}

//处理JPEG数据
//当采集完一帧JPEG数据后,调用此函数,切换JPEG BUF.开始下一帧采集.
void jpeg_data_process(void)
{
        u16 i;
        u16 rlen;//剩余数据长度
        u32 *pbuf;

        if(jpeg_data_ok==0)        //jpeg数据还未采集完?
        {
                DMA_Cmd(DMA2_Stream1,DISABLE);                //停止当前传输
                while(DMA_GetCmdStatus(DMA2_Stream1) != DISABLE);        //等待DMA2_Stream1可配置
                rlen=jpeg_dma_bufsize-DMA_GetCurrDataCounter(DMA2_Stream1);//得到剩余数据长度        
                pbuf=jpeg_data_buf+jpeg_data_len;//偏移到有效数据末尾,继续添加
                if(DMA2_Stream1->CR&(1<<19))
                        for(i=0;i<rlen;i++)
                                pbuf[i]=jpeg_buf1[i];//读取buf1里面的剩余数据
                else
                        for(i=0;i<rlen;i++)
                                pbuf[i]=jpeg_buf0[i];//读取buf0里面的剩余数据
                jpeg_data_len+=rlen;                        //加上剩余长度
                jpeg_data_ok=1;                                 //标记JPEG数据采集完按成,等待其他函数处理
        }
        if(jpeg_data_ok==2)        //上一次的jpeg数据已经被处理了
        { DMA_SetCurrDataCounter(DMA2_Stream1,jpeg_dma_bufsize);//传输长度为jpeg_buf_size*4字节
                DMA_Cmd(DMA2_Stream1,ENABLE); //重新传输
                jpeg_data_ok=0;                                        //标记数据未采集
                jpeg_data_len=0;                                //数据重新开始
        }
                        printf("jpeg_data_process success \r\n");               
}
当判断一帧数据采集完成,会启动DMA传输,传输完成会触发DMA中断,最后模式切换到SD卡模式,进行图片的保存。
void DMA2_Stream1_IRQHandler(void)
{        
        if(DMA_GetFlagStatus(DMA2_Stream1,DMA_FLAG_TCIF1)==SET)//DMA2_Steam1,传输完成标志
        {  
                 DMA_ClearFlag(DMA2_Stream1,DMA_FLAG_TCIF1);//清除传输完成中断
     dcmi_rx_callback();        //执行摄像头接收回调函数,读取数据等操作在这里面处理  
                 
        }                                                                                             
}

//jpeg数据接收回调函数
void jpeg_dcmi_rx_callback(void)
{
        u16 i;
        u32 *pbuf;
        pbuf=jpeg_data_buf+jpeg_data_len;//偏移到有效数据末尾
        if(DMA2_Stream1->CR&(1<<19))//buf0已满,正常处理buf1
        {
                for(i=0;i<jpeg_dma_bufsize;i++)pbuf[i]=jpeg_buf0[i];//读取buf0里面的数据
                jpeg_data_len+=jpeg_dma_bufsize;//偏移
        }else //buf1已满,正常处理buf0
        {
                for(i=0;i<jpeg_dma_bufsize;i++)pbuf[i]=jpeg_buf1[i];//读取buf1里面的数据
                jpeg_data_len+=jpeg_dma_bufsize;//偏移
        }         
}
我这里保存的是BMP格式,所以有一个转码处理,这里也贴一下。
//BMP编码函数
//将当前LCD屏幕的指定区域截图,存为16位格式的BMP文件 RGB565格式.
//保存为rgb565则需要掩码,需要利用原来的调色板位置增加掩码.这里我们已经增加了掩码.
//保存为rgb555格式则需要颜色转换,耗时间比较久,所以保存为565是最快速的办法.
//filename:存放路径
//x,y:在屏幕上的起始坐标  
//mode:模式.0,仅仅创建新文件的方式编码;1,如果之前存在文件,则覆盖之前的文件.如果没有,则创建新的文件.
//返回值:0,成功;其他,错误码.  
u8 bmp_encode(u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 mode)
{               
        u32 i=0,num=0;
        u32* pbuf;
        u16* pbuf_Data;
        FIL* f_bmp;
        u16 bmpheadsize;                        //bmp头大小                    
         BITMAPINFO hbmp;                        //bmp头         
        u8 res=0;
        u16 tx,ty;                                           //图像尺寸
        u16 *databuf;                                //数据缓存区地址                  
        u16 pixcnt;                                           //像素计数器
        u16 bi4width;                               //水平像素字节数           
        if(width==0||height==0)return PIC_WINDOW_ERR;        //区域错误
        if((x+width-1)>lcddev.width)return PIC_WINDOW_ERR;                //区域错误
        if((y+height-1)>lcddev.height)return PIC_WINDOW_ERR;        //区域错误
         
#if BMP_USE_MALLOC == 1        //使用malloc        
        databuf=(u16*)pic_memalloc(1024);                //开辟至少bi4width大小的字节的内存区域 ,对240宽的屏,480个字节就够了.
        if(databuf==NULL)return PIC_MEM_ERR;                //内存申请失败.
        f_bmp=(FIL *)pic_memalloc(sizeof(FIL));        //开辟FIL字节的内存区域
        if(f_bmp==NULL)                                                                //内存申请失败.
        {                 
                pic_memfree(databuf);
                return PIC_MEM_ERR;                                
        }         
#else
        databuf=(u16*)bmpreadbuf;
        f_bmp=&f_bfile;
#endif              
        bmpheadsize=sizeof(hbmp);//得到bmp文件头的大小   
        mymemset((u8*)&hbmp,0,sizeof(hbmp));//置零空申请到的内存.            
        hbmp.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);//信息头大小
        hbmp.bmiHeader.biWidth=width;                 //bmp的宽度
        hbmp.bmiHeader.biHeight=height;         //bmp的高度
        hbmp.bmiHeader.biPlanes=1;                         //恒为1
        hbmp.bmiHeader.biBitCount=16;                 //bmp为16位色bmp
        hbmp.bmiHeader.biCompression=BI_BITFIELDS;//每个象素的比特由指定的掩码决定。
         hbmp.bmiHeader.biSizeImage=hbmp.bmiHeader.biHeight*hbmp.bmiHeader.biWidth*hbmp.bmiHeader.biBitCount/8;//bmp数据区大小
                                    
        hbmp.bmfHeader.bfType=((u16)'M'<<8)+'B';//BM格式标志
        hbmp.bmfHeader.bfSize=bmpheadsize+hbmp.bmiHeader.biSizeImage;//整个bmp的大小
           hbmp.bmfHeader.bfOffBits=bmpheadsize;//到数据区的偏移

        hbmp.RGB_MASK[0]=0X00F800;                         //红色掩码
        hbmp.RGB_MASK[1]=0X0007E0;                         //绿色掩码
        hbmp.RGB_MASK[2]=0X00001F;                         //蓝色掩码

        if(mode==1)res=f_open(f_bmp,(const TCHAR*)filename,FA_READ|FA_WRITE);//尝试打开之前的文件
         if(mode==0||res==0x04)res=f_open(f_bmp,(const TCHAR*)filename,FA_WRITE|FA_CREATE_NEW);//模式0,或者尝试打开失败,则创建新文件                  
         if((hbmp.bmiHeader.biWidth*2)%4)//水平像素(字节)不为4的倍数
        {
                bi4width=((hbmp.bmiHeader.biWidth*2)/4+1)*4;//实际要写入的宽度像素,必须为4的倍数.        
        }else bi4width=hbmp.bmiHeader.biWidth*2;                //刚好为4的倍数         
         if(res==FR_OK)//创建成功
        {
                res=f_write(f_bmp,(u8*)&hbmp,bmpheadsize,&bw);//写入BMP首部  

                pixcnt=0;
                pbuf=(u32*)jpeg_data_buf;
//                pbuf_Data = (u32*)jpeg_data_buf;

        for (i = 16; i < 32; i++)
        {
                pbuf[i]=0x0;
//                pbuf_Data=pbuf&0x0000FFFF;

        }
                f_write(f_bmp,(u32*)pbuf,jpeg_data_len,&bw);
               
                f_close(f_bmp);
        }
//         if(res==FR_OK)//创建成功
//        {
//                res=f_write(f_bmp,(u8*)&hbmp,bmpheadsize,&bw);//写入BMP首部  
//                for(ty=y+height-1;hbmp.bmiHeader.biHeight;ty--){
//                        
//                }
//                pixcnt=0;
//                pbuf=(u32*)jpeg_data_buf;
//                f_write(f_bmp,(u32*)pbuf,jpeg_data_len*4,&bw);
//               
//                f_close(f_bmp);
//        }               
#if BMP_USE_MALLOC == 1        //使用malloc        
        pic_memfree(databuf);         
        pic_memfree(f_bmp);                 
#endif        
        return res;
}
因为项目不是现在做的,所以讲起来可能有些乱,这里附上百度网盘的下载链接,因为文件过大,没法上传附件,望谅解。
链接的隐藏是为了获取回复量,有点私心,各位大侠高抬贵手。感激不尽。
游客,如果您要查看本帖隐藏内容请回复

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 100.00 元 2021-03-04
理由:恭喜通过原创文章审核!请多多加油哦!

沙发
xyz549040622| | 2021-2-7 22:10 | 只看该作者
坐个沙发支持一下。

使用特权

评论回复
评论
呐咯密密 2021-2-8 11:16 回复TA
感谢支持,祝大佬新年快乐,牛年大吉 
呐咯密密 2021-2-8 11:16 回复TA
感谢支持,祝大佬新年快乐,牛年大吉 
板凳
goyhuan| | 2021-2-8 10:56 | 只看该作者
看看

使用特权

评论回复
地板
xiaoqizi| | 2021-3-2 18:53 | 只看该作者
好想看楼主的pose啊

使用特权

评论回复
评论
呐咯密密 2021-3-3 09:31 回复TA
这个可不行哦 
5
木木guainv| | 2021-3-2 18:54 | 只看该作者
效果真的很不错啊

使用特权

评论回复
6
磨砂| | 2021-3-2 18:55 | 只看该作者
分辨率能达到多少啊

使用特权

评论回复
7
晓伍| | 2021-3-2 18:58 | 只看该作者
代码的解释很详细啊

使用特权

评论回复
8
八层楼| | 2021-3-2 19:00 | 只看该作者
正好可以拿来用用啊 谢谢

使用特权

评论回复
9
keke| | 2021-3-6 14:01 | 只看该作者

看看楼主的pose啊

使用特权

评论回复
10
crazyren| | 2021-3-8 14:00 | 只看该作者
看看...

使用特权

评论回复
11
zhouyong77| | 2021-3-8 19:00 | 只看该作者
谢谢楼主分享经验

使用特权

评论回复
12
qslt1983| | 2021-3-9 09:35 | 只看该作者
高手,学习学习。

使用特权

评论回复
13
gyh974| | 2021-3-9 09:43 | 只看该作者
楼主牛,成本确实是很关键,能省一点是一点

使用特权

评论回复
14
自己的灌饼| | 2021-3-9 11:38 | 只看该作者
楼主厉害,成本是关键指标。

使用特权

评论回复
15
触觉的爱| | 2021-3-9 19:02 | 只看该作者
就差成品效果 图展示了

使用特权

评论回复
16
wandersky| | 2021-3-10 19:03 | 只看该作者
mark stm32驱动摄像头。

使用特权

评论回复
17
yangbinge| | 2021-3-11 09:06 | 只看该作者
支持一下

使用特权

评论回复
18
wjzhe| | 2021-3-11 11:14 | 只看该作者
支持一下

使用特权

评论回复
19
YDMCP| | 2021-3-13 07:09 | 只看该作者
牛 谢谢

使用特权

评论回复
20
ehua| | 2021-3-14 08:15 | 只看该作者
如果您要查看本帖隐藏内容请回复

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

497

主题

3881

帖子

47

粉丝