前言:无意发现之前学STM32做的笔记。。。有点感慨。。有些内容不全。各位自行查找。整体知识量很大
 

基础知识

单片机

单片机(MCU)的组成:

  • CPU:处理指令和控制单片机的操作。

  • 内存:包括随机访问存储器(RAM)和只读存储器(ROM),用于存储程序代码和运行时数据。

  • 输入输出接口(I/O):允许单片机与外部世界(如传感器、显示屏、按键等)进行通信。

  • 外设:如定时器、串行通讯接口(如USART、SPI、I2C等)、模数转换器(ADC)、数模转换器(DAC)等。

  • 处理器核心(CPU):

    是一个CPU核心,设计由ARM公司提供。它是一种高性能的处理器核心,专为低成本、低功耗的嵌入式应用设计,提供了丰富的指令集和高效的中断处理能力。在这个上下文中,Cortex-M3是CPU

    操作系统

    CMSIS-RTOS

  • 定义:CMSIS-RTOS是Cortex Microcontroller Software Interface Standard(CMSIS)的一部分,由ARM公司提出。它定义了一个与硬件无关的RTOS接口标准,旨在提高应用程序的可移植性和可重用性。CMSIS-RTOS是一个RTOS API规范,而不是RTOS本身。

  • 目的:主要目的是为了在不同的RTOS实现之间提供一个统一的接口,使得基于Cortex-M系列处理器的应用程序能够更容易地在不同RTOS之间迁移。

  • 实现:多个RTOS提供了CMSIS-RTOS API的实现,包括FreeRTOS、RTX(由ARM提供的RTOS)、以及其他第三方RTOS。

  • FreeRTOS

  • 定义:FreeRTOS是一个开源的实时操作系统,针对嵌入式设备设计。它提供了多任务处理、时间管理、同步机制等实时操作系统的基本功能。

  • 目的:提供一个轻量级、简单、易于使用的RTOS,用于管理复杂的嵌入式应用中的多任务和时间。FreeRTOS通过其API直接提供这些功能,而不仅仅是一个接口规范。

  • 实现:FreeRTOS本身就是一个完整的RTOS实现,它也提供了符合CMSIS-RTOS API规范的包装层(Wrapper),使得基于FreeRTOS的应用程序能够使用CMSIS-RTOS API。

  • 堆 heap

    含义:就是一块空闲的内存,需要提供管理函数

    malloc:从堆里划出一块空间给程序使用

    free:用完后,再把它标记为"空闲"的,可以再次使用

    栈 stack

    函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中

    可以从堆中分配一块空间用作栈

    钩子函数(Hook Functions)

    钩子函数提供了一种机制,允许开发者在RTOS的关键点插入自己的函数或处理逻辑,例如在每个tick周期、任务切换、空闲任务运行时执行用户定义的代码。这些函数允许开发者监控RTOS的运行状态、收集统计信息或添加自定义的行为。


    Delay函数

    标准库

     ​
     static u8  fac_us=0; //us延时倍乘数             
     static u16 fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
     ​
     /****************************************************************************
     * 名    称: delay_init()
     * 功    能:延时函数初始化
     * 入口参数:无
     * 返回参数:无
     * 说    明:
     ****************************************************************************/
     void delay_init()
     {
         SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
         fac_us=SYSCLK/8;     
         fac_ms=(u16)fac_us*1000; //每个ms需要的systick时钟数   
     }                                   
     ​
     /****************************************************************************
     * 名    称: void delay_us(u32 nus)
     * 功    能:延时nus
     * 入口参数:要延时的微妙数
     * 返回参数:无
     * 说    明:nus的值,不要大于798915us
     ****************************************************************************/
     void delay_us(u32 nus)
     {       
         u32 midtime;             
         SysTick->LOAD=nus*fac_us; //时间加载             
         SysTick->VAL=0x00;        //清空计数器
         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数 
         do
         {
             midtime=SysTick->CTRL;
         }
         while((midtime&0x01)&&!(midtime&(1<<16)));//等待时间到达   
         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
         SysTick->VAL =0X00;       //清空计数器    
     }
     ​
     /****************************************************************************
     * 名    称: void delay_xms(u16 nms)
     * 功    能:延时nms
     * 入口参数:要延时的毫妙数
     * 返回参数:无
     * 说    明:SysTick->LOAD为24位寄存器,所以,最大延时为: nms<=0xffffff*8*1000/SYSCLK
                 对168M条件下,nms<=798ms 
     ****************************************************************************/
     void delay_xms(u16 nms)
     {                 
         u32 midtime;           
         SysTick->LOAD=(u32)nms*fac_ms;//时间加载(SysTick->LOAD为24bit)
         SysTick->VAL =0x00;           //清空计数器
         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数  
         do
         {
             midtime=SysTick->CTRL;
         }
         while((midtime&0x01)&&!(midtime&(1<<16)));//等待时间到达   
         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
         SysTick->VAL =0X00;       //清空计数器           
     } 
     ​
     /****************************************************************************
     * 名    称: void delay_ms(u16 nms)
     * 功    能:延时nms
     * 入口参数:要延时的毫妙数
     * 返回参数:无
     * 说    明:nms:0~65535
     ****************************************************************************/
     void delay_ms(u16 nms)
     {        
         u8 repeat=nms/540;  //这里用540,是考虑到某些客户可能超频使用,
                                       //比如超频到248M的时候,delay_xms最大只能延时541ms左右了
         u16 remain=nms%540;
         while(repeat)
         {
             delay_xms(540);
             repeat--;
         }
         if(remain)delay_xms(remain);
     }   

    HAL库

     /****************************************************************************
     * 名    称: void delay_init(uint16 sysclk)
     * 功    能:delay初始化
     * 入口参数:sysclk 系统时钟 单位M
     * 返回参数:无
     * 说    明:nus的值,不要大于798915us
     ****************************************************************************/
     void delay_init(uint16 sysclk)
     {
         SysTick->CTRL = 0;      /*滴答定时器的状态控制寄存器 清零  目的清空HAL_Init自动配置的参数*/
         HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK_DIV8);
         fac_us=SYSCLK/8;       /*结合上面代码 得到真正计数频率 获得1us的时基  即1us数fac_us次*/
     }                                   
     ​
     /****************************************************************************
     * 名    称: void delay_us(u32 nus)
     * 功    能:延时nus
     * 入口参数:要延时的微妙数
     * 返回参数:无
     * 说    明:nus的值,不要大于798915us
     ****************************************************************************/
     void delay_us(u32 nus)
     {       
         u32 midtime;             
         SysTick->LOAD=nus*fac_us; //时间加载             
         SysTick->VAL=0x00;        //清空计数器
         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;          //开始倒数 
         do
         {
             midtime=SysTick->CTRL;
         }
         while((midtime&0x01)&&!(midtime&(1<<16)));//等待时间到达   
         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;       //关闭计数器
         SysTick->VAL =0X00;       //清空计数器    
     }
     ​
     /****************************************************************************
     * 名    称: void delay_ms(u16 nms)
     * 功    能:延时nms
     * 入口参数:要延时的毫妙数
     * 返回参数:无
     * 说    明: 
     ****************************************************************************/
     void delay_ms(u16 nms)
     {
         u32 i;
         for(i=0;i<nms;i++) delay_us(1000);
     } 


    引脚重映射

    STM32的引脚可设置为可设置为:普通IO功能、复用功能、重映射功能。

    普通IO功能: 拉高、拉低 、悬空

    复用功能:TIM、USART、IIC

    重映射功能:将某些I/O口上面的功能映射到其他I/O口上面去


    WDG

    分类:窗口看门狗、独立看门狗

    IWDG独立看门狗 WWDG窗口看门狗
    复位 计数器减到0后 计数器T[5:0]减到0后、过早重装计数器
    中断 早期唤醒中断
    时钟源 LSI(40KHz) PCLK1(36MHz)
    预分频系数 4、8、32、64、128、256 1、2、4、8
    计数器 12位 6位(有效计数)
    超时时间 0.1ms~26214.4ms 113us~58.25ms
    喂狗方式 写入键寄存器,重装固定值RLR 直接写入计数器,写多少重装多少
    防误操作 键寄存器和写保护
    用途 独立工作,对时间精度要求较低 要求看门狗在精确计时窗口起作用

    调用函数:

     void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);
     void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);
     void IWDG_SetReload(uint16_t Reload);
     void IWDG_ReloadCounter(void);
     void IWDG_Enable(void);
     FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);
     ​
     FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
     void RCC_ClearFlag(void);


    GPIO

    对于407VET6,一个GPIO的模式需要配置以下三个参数:

         GPIO_InitStructure.GPIO_Mode =          
         GPIO_InitStructure.GPIO_OType 
         GPIO_InitStructure.GPIO_PuPd = ;
     typedef enum
         { 
           GPIO_Mode_IN   = 0x00, /*!< GPIO Input Mode */
           GPIO_Mode_OUT  = 0x01, /*!< GPIO Output Mode */
           GPIO_Mode_AF   = 0x02, /*!< GPIO Alternate function Mode */
           GPIO_Mode_AN   = 0x03  /*!< GPIO Analog Mode */
         }GPIOMode_TypeDef;
     typedef enum
     { 
       GPIO_OType_PP = 0x00,     /*推挽模式*/
       GPIO_OType_OD = 0x01
     }GPIOOType_TypeDef;
     typedef enum
     { 
       GPIO_PuPd_NOPULL = 0x00,
       GPIO_PuPd_UP     = 0x01,
       GPIO_PuPd_DOWN   = 0x02
     }GPIOPuPd_TypeDef;


    IIC

    简介:一个时钟线SCL、一个数据线SDA


    系统滴答定时器

    系统滴答定时器优先级与HAL_Delay()的执行有关。

    HAL_Delay()只能在主函数或者优先级低于系统滴答定时器的中断中执行


    SPI通信

    引脚:

  • MOSI:复用推挽

  • MISO:上拉输入

  • SCK:复用推挽

  • NSS:推挽

  • SPI_InitTypeDef结构体

         SPI_InitStructure.SPI_Mode              //模式,指定32是主机(Master)还是从机(Slave)
         SPI_InitStructure.SPI_Direction         //方向,可裁剪引脚
         SPI_InitStructure.SPI_DataSize          //数据宽度,选择为8位还是16位 一般8位
         SPI_InitStructure.SPI_FirstBit          //先行位,选择高位先行还是低位先行 一般高位
         SPI_InitStructure.SPI_BaudRatePrescaler  //波特率分频系数 PCLK/PSC=时钟频率    PCLK由APB线决定
         SPI_InitStructure.SPI_CPOL              //SPI极性,默认低电平还是高,一般低极性(低电平)
         SPI_InitStructure.SPI_CPHA              //SPI相位,选第一个时钟边沿采样,极性和相位决定选择SPI模式0
         SPI_InitStructure.SPI_NSS               //NSS,选择由软件控制,可以自己选引脚
         SPI_InitStructure.SPI_CRCPolynomial      //CRC多项式,暂时用不到,给默认值7
         SPI_Init(SPI1, &SPI_InitStructure);      //将结构体变量交给SPI_Init,配置SPI1

    软件SPI通信核心:交换一个字节

    1. 准备好写的数据 0或1

    2. 把SCK拉高

    3. 瞅一眼接受的是0还是1

    4. 把SCK拉低

     //SPI交换一个字节
     uint8_t SPI_SwapByte(uint8_t Byte)
     {
         uint8_t i,ByteReceive=0x00;                  //白纸等待往里写数据
         for(i=0;i<8;i++)
         {
             W_MOSI(Byte&(0x80>>i));                  //写数据的第i位
             W_SCK(1);
             if(R_MISO()==1)                          //对面数据第i位有1
             {
                 ByteReceive=ByteReceive|(0x80>>i);   //ByteReceive第i位写个1
             }
             W_SCK(0);
         }
         return ByteReceive;
     }


    NVIC:

    NVIC:主要作用就是配置中断优先级的

     void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);                  //用来中断分组  (选定一个NVIC)
     void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);                          //NVIC初始化
     void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);             //设置中断向量表
     void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);     //低功耗模式配置
     void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
     /*NVIC配置*/
         NVIC_InitTypeDef NVIC_InitStructure;                        //定义结构体变量
         NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;        //选择配置NVIC的EXTI15_10线
         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;             //指定NVIC线路使能
         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   //指定NVIC线路的抢占优先级为1
         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;          //指定NVIC线路的响应优先级为1
         NVIC_Init(&NVIC_InitStructure);                             //将结构体变量交给NVIC_Init,配置NVIC外设

    AFIO:

    作用:

  • 配置端口复用

  • 配置端口重映射

  • 中断引脚选择

  • 标准库相关函数:

     void GPIO_AFIODeInit(void);                                                 //清除AFIO的所有配置
     void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);        //锁定某个引脚配置
     void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);   //AFIO事件输出功能
     void GPIO_EventOutputCmd(FunctionalState NewState);
     void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);        //选择引脚重映射
     void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);      //配置外部中断线
     void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);           //以太网相关

    中断:

    按中断源分类:外部中断和内部中断

    按中断去向分类:中断相应和事件响应(是触发代码执行还是触发外设动作)

    外部配置:

    相关函数:

    void EXTI_DeInit(void);										//清除EXTI的所有配置
    void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);			  //EXTI初始化
    void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);   	   //给EXTI_InitStruct赋默认值
    void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);			  //直径让软件给中断线产生一次中断
    FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);			  //查看中断标志位  主程序里用
    void EXTI_ClearFlag(uint32_t EXTI_Line);			          //清除中断标志位  主程序里用
    ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);			      //查看中断标志位的状态   中断里用
    void EXTI_ClearITPendingBit(uint32_t EXTI_Line);		      //清除中断标志位的状态   中断里用
    //差别  上面一组仅仅查看标志位  下面一组是判断中断是否产生

    配置过程:开启时钟——> GOIO配置——> AFIO选定 ——>中断配置(外部中断+NVIC分组) ——> 中断函数

    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟
    	/*GPIO初始化*/
    	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB14引脚初始化为上拉输入
    	/*AFIO选择中断引脚*/
    	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
    	/*EXTI初始化*/
    	EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量
    	EXTI_InitStructure.EXTI_Line = EXTI_Line14;					//选择配置外部中断的14号线
    	EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
    	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			 //指定外部中断线为中断模式(另一个事件模式)
    	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		 //指定外部中断线为下降沿触发
    	EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设
    	/*NVIC中断分组*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
        /*即抢占优先级范围:0~3,响应优先级范围:0~3此分组配置在整个工程中仅需调用一次若有多个中断,
          可以把此代码放在main函数内,while循环之前,若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置*/
    	/*NVIC配置*/
    	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
    	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10线
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
    	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
    /**
      * 函    数:EXTI15_10外部中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void EXTI15_10_IRQHandler(void)
    {
    	if (EXTI_GetITStatus(EXTI_Line14) == SET)		//判断是否是外部中断14号线触发的中断,在中断中用
    	{
    		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
    		{
    			...									//外部中断每产生一次所要执行的内容
    		}
    		EXTI_ClearITPendingBit(EXTI_Line14);		//清除外部中断14号线的中断标志位
    													//中断标志位必须清除
    													//否则中断将连续不断地触发,导致主程序卡死
    	}
    }

    关键:

    1,GOIO与边沿触发

    默认高电平 ——> 上拉输入 ——> 下降沿触发

    默认低电平 ——> 下拉输入 ——> 上升沿触发

    所以,按键一段IO口,一端高电平,则下拉输入, 上升沿触发。反之同理。

    2,外部中断线:检测到电平变化便产生中断的引脚 每个数字的IO口只能有一个中断进入 NVIC(从EXTI_InitTypeDef可看出)

    3,外部中断的NVIC(NVIC_IRQChannel):

    EXTI0_IRQn			——>数字为 0的IO引脚都走这个通道  
    EXTI1_IRQn			——>数字为 1的IO引脚都走这个通道 
    EXTI2_IRQn 			——>数字为 2的IO引脚都走这个通道 
    EXTI3_IRQn			——>数字为 3的IO引脚都走这个通道 
    EXTI4_IRQn			——>数字为 4的IO引脚都走这个通道 
    EXTI9_5_IRQn	    ——>数字为 5到 9 的IO引脚都走这个通道  
    EXTI15_10_IRQn      ——>数字为 10到 15 的IO引脚都走这个通道
        //部分通道所有F1xx都有,部分依据型号而定

    4,中断函数的名字(与中断通道相对应)

    EXTI0_IRQHandler
    EXTI1_IRQHandler
    EXTI2_IRQHandler
    EXTI3_IRQHandler
    EXTI4_IRQHandler
    EXTI9_5_IRQHandler
    EXTI15_10_IRQHandler

    5,中断模式和时间模式

    EXTI_Mode_Interrupt          //当外部引脚触发了中断条件时,微控制器会立即跳转到中断服务程序(ISR)执行相应的中断处理程序。
    EXTI_Mode_Event 			//不会立即执行中断服务程序,而是将事件标志设置为待处理状态。

    定时器中断: 基本配置:

    
    

    串口:

    串口配置:

    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX, ENABLE);	//开启GPIOX的时钟
    	/*GPIO初始化*/
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA9引脚初始化为复用推挽输出
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA10引脚初始化为上拉输入
    	/*USART初始化*/
    	USART_InitTypeDef USART_InitStructure;										//定义结构体变量
    	USART_InitStructure.USART_BaudRate = 9600;									//波特率
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//硬件流控制,不需要
    	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;				   //模式,发送模式和接收模式均选择
    	USART_InitStructure.USART_Parity = USART_Parity_No;						   	//奇偶校验,不需要
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;					   	//停止位,选择1位
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//字长,选择8位
    	USART_Init(USART1, &USART_InitStructure);				//将结构体变量交给USART_Init,配置USART1
    	/*中断输出配置*/
    	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
    	/*NVIC中断分组*/
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
    	/*NVIC配置*/
    	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;			 //选择配置NVIC的USART1线
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				 //指定NVIC线路使能
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
    	NVIC_Init(&NVIC_InitStructure);							//将结构体变量交给NVIC_Init,配置NVIC外设
    	/*USART使能*/
    	USART_Cmd(USART1, ENABLE);								//使能USART1,串口开始运行

    硬件流控制:

    作用:当两台设备进行串口通信,假如他们对数据的处理速度不同。如果接收端数据缓冲区已满,则此时继续发送来的数据就会丢失。使用流控机制时,当接收端数据处理能力饱和时,就发出“不再接收”的信号,发送端就停止发送,直到接收端处理能力释放,发送“可以继续发送”的信号给发送端时,发送端才继续发送数据

    本质:在串口的基础上加上RTS和CTS来控制数据传输 易用软件模拟

    中断配置:

    	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
    	USART_IT_TXE 发送中断								   //一般只开启发送和接受中断
        USART_IT_RXNE 接收中断	

    串口重定向

    底层函数:

    函数名前有__,如

    int __io_putchar(int ch) __attribute__((weak));

    表明该函数与系统有关,谨慎调用。


    定时器

    定时器模式:

    基础知识:

    时基单元: 预分频器、计数器、自动重装器

    定时器输出PWM波形:

    理解本质:利用输出比较,在定时器计数值大于某一值时,输出1;小于某一值,输出0

    所需外设:一个定时器的一个通道可输出一个PWM波

    编码盘测速:

    所需配置:一个定时器,及其TIMx1、TIMx2两个通道


    RTC&BKP&PWR

    RTC(实时时钟)

    注:以下特征以stm32f407VET6为准

    本质:

  • 配置一个频率为1HZ的时钟

  • 需注意和BKP结合使用

  • 需注意和低功耗模式结合使用

  • 核心:

  • 访问存储数据的影子寄存器 可写入读出

  • BCD码存储数据

  • 由备用电源接口VBAT供电

  • 可连接三个中断:秒中断、溢出中断、闹钟中断

  • 开启PWR,BKP时钟才能使能RTC,BKP访问(f103)

  • 开启PWR时钟才能使能RTC,BKP访问(f403)

  • 进入配置模式才能写 即关闭 闹钟/唤醒中断

  • 软件需等寄存器同步标志被置1才能读

  • RTC时钟源:

  • LSI:(Low Speed Internal clock)内部的低速振荡器 32.768KHZ

  • LSE:(Low Speed External clock):外部低速振荡器,通常连一个32.768 kHz的石英。提供非常稳定的时钟信号,主用于RTC

  • HSE(High Speed External clock):外部高速振荡器,可连接到一个4 MHz至26 MHz的石英或外部时钟源。可通过PLL进一步倍频

  • PLL:锁相环

  • LSE为了省电默认关闭,需要手动开启

  • 配置流程:

    1. 启PWR,BKP时钟使能RTC的访问(配置时钟需要访问寄存器)

    2. 启动时钟并选择时钟

    3. 配置预分频器使得时钟为1HZ

    4. 写入CNT,相当于给一个初始时间

    5. 选择配置中断(f1每秒、溢出、闹钟)(f4唤醒中断、溢出、闹钟、入侵检测)

    配置时钟:

    void        RCC_LSEConfig(uint8_t RCC_LSE);			/*配置外部低速时钟*/
    void        RCC_HSEConfig(uint8_t RCC_HSE);			/*配置外部高速时钟*/
    void        RCC_LSICmd(FunctionalState NewState);	/*控制内部低速时钟*/
    void        RCC_HSICmd(FunctionalState NewState);	/*使能内部高速时钟*/
    void        RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);	/*选择RTC时钟源*/
    void        RCC_RTCCLKCmd(FunctionalState NewState);		/*使能RTC时钟*/
    FlagStatus  RCC_GetFlagStatus(uint8_t RCC_FLAG);			/*获得RCC标志位,为1才算启动完成*/

    配置RTC

    RCC_RTCCLKCmd(ENABLE); 							//使能 RTC 时钟
    RTC_InitStructure.RTC_AsynchPrediv = 0x7F;		 //RTC 异步分频系数(1~0X7F)
    RTC_InitStructure.RTC_SynchPrediv = 0xFF;		 //RTC 同步分频系数(0~7FFF)
    RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;//24 小时格式
    RTC_Init(&RTC_InitStructure);//初始化 RTC 参数

    使用RTC

    typedef struct
    {
        uint8_t RTC_WeekDay;	/*星期几*/
        uint8_t RTC_Month;		/*月份*/
        uint8_t RTC_Date;		/*日期*/
        uint8_t RTC_Year;		/*年份*/
    }RTC_DateTypeDef;
    typedef struct
    {
        uint8_t RTC_Hours;		/*小时*/
        uint8_t RTC_Minutes;
        uint8_t RTC_Seconds;
        uint8_t RTC_H12;
    }RTC_TimeTypeDef;
    ErrorStatus RTC_SetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);	/*设置RTC时间 bin or BCD*/
    ErrorStatus RTC_SetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);	/*设置RTC日期 bin or BCD*/
    void RTC_GetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);			/*获取RTC时间 bin or BCD*/
    void RTC_GetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);			/*获取RTC日期 bin or BCD*/
    

    配置闹钟

    typedef struct
    {
      RTC_TimeTypeDef RTC_AlarmTime;     /*闹钟时间 */
      uint32_t RTC_AlarmMask;            /*闹钟时间掩码 */
      uint32_t RTC_AlarmDateWeekDaySel;  /* */
      uint8_t RTC_AlarmDateWeekDay;      /*第三个参数决定*/
    }RTC_AlarmTypeDef;
    void RTC_SetAlarm(uint32_t RTC_Format, uint32_t RTC_Alarm, RTC_AlarmTypeDef* RTC_AlarmStruct);/*设置闹钟*/
    void RTC_GetAlarm(uint32_t RTC_Format, uint32_t RTC_Alarm, RTC_AlarmTypeDef* RTC_AlarmStruct);/*读取闹钟*/
    ErrorStatus RTC_AlarmCmd(uint32_t RTC_Alarm, FunctionalState NewState); 	/*闹钟使能*/
    

    配置唤醒中断(F4)

    void RTC_Set_WakeUp(void)
    {
        
        RTC_WakeUpCmd(DISABLE);                               //关闭 WAKE UP
        
        RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits);//每秒一次中断
        RTC_SetWakeUpCounter(0);                              //WAKE UP计数器值(RTC时钟源频率 / WAKE UP时钟周期) - 1
        RTC_ClearITPendingBit(RTC_IT_WUT); //清除 RTC WAKE UP 的标志
        EXTI_ClearITPendingBit(EXTI_Line22);//清除 LINE22 上的中断标志位
        RTC_ITConfig(RTC_IT_WUT,ENABLE);//开启 WAKE UP 定时器中断
        
        RTC_WakeUpCmd( ENABLE);//开启 WAKE UP 定时器
        
        EXTI_InitTypeDef EXTI_InitStructure;
        EXTI_InitStructure.EXTI_Line = EXTI_Line22;				//看EXTI库配置
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;		//中断模式,执行中断函数
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  //上升沿触发(看手册)
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;			 
        EXTI_Init(&EXTI_InitStructure);						
        
        NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = RTC_WKUP_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//抢占优先级 1
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//响应优先级 2
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
        NVIC_Init(&NVIC_InitStructure);//配置
    }

    BKP(备份寄存器)

    本质:相当于一个EEPROM

    核心:

    掉电丢失

    由VBAT供电

    库函数:

    /*f103有专门的文件,f407在RTC文件里*/
    /*初始化 在103还需多一步 使能BKP时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//使能 PWR 时钟
    PWR_BackupAccessCmd(ENABLE);   					  //使能BKP 
    RTC_Read_Val = RTC_ReadBackupRegister(RTC_BKP_DR0);			//读函数
    RTC_WriteBackupRegister(RTC_BKP_DR0,RTC_Write_Val);			//写函数

    低功耗模式


    DMA

    定义:Direct Memory Access直接存储器访问

    目的:绕过CPU传输数据

    四种方式:

  • 外设到内存

  • 内存到外设

  • 内存到内存

  • 外设到外设

  • 配置主要参数:

  • 源地址、

  • 目标地址

  • 传输数据量

  • 搬运过程(抽象): 源地址 ——> 一排寄存器

    目的地址 ——> 一排寄存器

    把源地址寄存器的数值复制到目的寄存器地址数值

    四种转运模式模式:

    源地址第一个寄存器 ——> 一遍遍复制到目的地址第一个寄存器

    源地址第一个寄存器 ——> 一次次按顺序复制到目的地址寄存器

    源地址寄存器按顺序 ——> 复制到目的地址第一个寄存器

    源地址寄存器按顺序 ——> 复制到目的地址对应的寄存器

    转运数据: 类似数据类型强制转换:

    低位转高位 ——> 补0

    高位转低位 ——> 截断

    参数解读(从外设到存储器):

    外设配置

    	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;						  //外设基地址,给定形参AddrA
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; 	//外设数据宽度,选择字节
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;			   //外设地址自增,选择使能

    存储器配置

    	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;							//存储器基地址,给定形参AddrB
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			 //存储器数据宽度,选择字节
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器地址自增,选择使能

    转运配置:

        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;						//数据传输方向,选择由外设到存储器
    	DMA_InitStructure.DMA_BufferSize = Size;								//转运的数据大小(转运次数)
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//模式,选择正常模式
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;								//存储器到存储器,选择使能
    	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;					//优先级,选择中等 
    /*DMA_Mode:
    正常模式:   计数器到0就停转
    循环模式:   计数器到0就自动重装, 数据指针恢复到原来的位置,然后开启下一轮转存
    DMA_M2M:
    软件触发:以最快速度传输完成	不能和循环模式同时用 否则会停不下来
    硬件触发:
    DMA_Priority:
    第一阶段(==软件阶段==):每个通道的优先级可在==DMA_CCRx==寄存器中设置,有四个等级:最高、高、中和低优先级。
    第二阶段(硬件阶段):如果两个请求有相同软件优先级,较低编号的通道比较高编号的通道有较高的优先级。
    (大容量芯片中,DMA1控制器拥有高于DMA2控制器的优先级。且多个请求通过逻辑或输入到DMA控制器,只能有一个请求有效。)*/

    函数解析:

    //清楚某一个DMA通道的配置
    void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);	
    //对某一个DMA通道进行初始化
    void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
    //给DMA_InitStruct结构体赋一个初始值
    void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
    //DMA使能
    void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
    /*DMA中断配置
    可以在数据传输错误   传输一半    传输完成时产生中断*/
    void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
    //写入传输计数器,指定将要转运的次数  写前要对DMA失去能
    void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 
    //DMA通道中剩余的传输单元数
    uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
    //DMA工作完成标志位  一般while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
    //清楚DMA工作完成标志位
    void DMA_ClearFlag(uint32_t DMAy_FLAG);
    //查询DMA中断产生情况 (SET or RESET) 
    ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
    //清楚DMA中断标志位
    void DMA_ClearITPendingBit(uint32_t DMAy_IT)

    DMA配置:

      * 函    数:DMA初始化
      * 参    数:AddrA 原数组的首地址
      * 参    数:AddrB 目的数组的首地址
      * 参    数:Size 转运的数据大小(转运次数)
      * 返 回 值:无
      */
    void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
    {
    	MyDMA_Size = Size;					//将Size写入到全局变量,记住参数Size
    	
    	/*开启时钟*/
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);						//开启DMA的时钟
    	
    	/*DMA初始化*/
    	DMA_InitTypeDef DMA_InitStructure;										//定义结构体变量
    	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;						//外设基地址,给定形参AddrA
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据宽度,选择字节
    	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;			//外设地址自增,选择使能
    	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;							//存储器基地址,给定形参AddrB
    	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//存储器数据宽度,选择字节
    	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					//存储器地址自增,选择使能
    	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;						//数据传输方向,选择由外设到存储器
    	DMA_InitStructure.DMA_BufferSize = Size;								//转运的数据大小(转运次数)
    	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//模式,选择正常模式
    	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;								//存储器到存储器,选择使能
    	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;					//优先级,选择中等
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);							//将结构体变量交给DMA_Init,配置DMA1的通道1
    	
    	/*DMA使能*/
    	DMA_Cmd(DMA1_Channel1, DISABLE);	//这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
    }
    
    /**
      * 函    数:启动DMA数据转运
      * 参    数:无
      * 返 回 值:无
      */
    void MyDMA_Transfer(void)
    {
    	DMA_Cmd(DMA1_Channel1, DISABLE);					//DMA失能,在写入传输计数器之前,需要DMA暂停工作
    	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);	//写入传输计数器,指定将要转运的次数
    	DMA_Cmd(DMA1_Channel1, ENABLE);						//DMA使能,开始工作
    	
    	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);	//等待DMA工作完成
    	DMA_ClearFlag(DMA1_FLAG_TC1);						//清除工作完成标志位
    }
    {...
        MyDMA_Init( AddrA,  AddrB,  Size);
        MyDMA_Transfer();
    }

    ADC

    四种模式:

  • 单次转换非扫描

  • 连续转换非扫描

  • 单次转换扫描

  • 连续转换扫描

  • 连续转换:转换了一次后,自动开始下一次转换

    扫描模式:转换一次后,下一次转换通道自动向下移动一位,否则仅转换当前通道

    Vref+参考电压引脚

    所需函数

    void ADC_DeInit(ADC_TypeDef* ADCx);								//ADC清除配置
    void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);//ADC初始化
    void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);			 //ADC结构体初始化
    
    void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);						//ADC启动
    void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);					//ADC+DMA启动
    void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);  //ADC中断配置
    
    void ADC_ResetCalibration(ADC_TypeDef* ADCx);					//复位校准
    FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);	  //获取复位校准状态
    void ADC_StartCalibration(ADC_TypeDef* ADCx);					//开始校准
    FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);			 //获取开始校准状态
    
    void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);	//软件启动ADC转换
    
    FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);			//获取ADC转换状态 ,看EOC标志位是否置1
    
    void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);		//配置ADC间断模式,每隔几通道
    void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);         	//启动间断模式
    
    

    配置过程:

    /*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    	
    	/*设置ADC时钟*/										   //ADC转换需要时间,频率太高会不稳定,所以需要分频
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);					    //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;			//配置成专用的模拟输入
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0引脚初始化为模拟输入
    	
    	/*规则组通道配置*/								/*分别是采样通道,采样时间,*/
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);		//规则组序列1的位置,配置为通道0
    
    	/*ADC初始化*/
    	ADC_InitTypeDef ADC_InitStructure;						//定义结构体变量
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;		//模式,选择独立模式,即单独使用ADC1
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//数据对齐,选择右对齐
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//外部触发,使用软件触发,不需要外部触发
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;		//连续转换,失能,每转换一次规则组序列后停止
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;			//扫描模式,失能,只转换规则组的序列1这一个位置
    	ADC_InitStructure.ADC_NbrOfChannel = 1;					//通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
    	ADC_Init(ADC1, &ADC_InitStructure);						//将结构体变量交给ADC_Init,配置ADC1
    	
    	/*ADC使能*/
    	ADC_Cmd(ADC1, ENABLE);									//使能ADC1,ADC开始运行
    	
    	/*ADC校准*/
    	ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准 复位校准
    	while (ADC_GetResetCalibrationStatus(ADC1) == SET);		  //等待复位校准完成
    	ADC_StartCalibration(ADC1);							    //开始校准
    	while (ADC_GetCalibrationStatus(ADC1) == SET);		      //等待校准完成

    读取过程:

    /**
      * 函    数:获取AD转换的值
      * 参    数:无
      * 返 回 值:AD转换的值,范围:0~4095
      */
    uint16_t AD_GetValue(void)
    {
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);					//软件触发AD转换一次
    	while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);	//等待EOC标志位,即等待AD转换结束
    	return ADC_GetConversionValue(ADC1);					//读数据寄存器,得到AD转换的结果
    }

    ADC+DMA

    ADC连续转换+DMA循环转运:

    /*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟
    	/*设置ADC时钟*/
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
    ...GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入
    /*规则组通道配置*/
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);	//规则组序列1的位置,配置为通道0
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);	//规则组序列2的位置,配置为通道1
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);	//规则组序列3的位置,配置为通道2
    	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);	//规则组序列4的位置,配置为通道3
    ...
    	ADC_Init(ADC1, &ADC_InitStructure);							//扫描模式+连续转换+软件触发
    ...
        DMA_Init(DMA1_Channel1, &DMA_InitStructure);				//地址自增+循环模式+ADC触发
    	/*DMA和ADC使能*/
    	DMA_Cmd(DMA1_Channel1, ENABLE);							//DMA1的通道1使能
    	ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能
    	ADC_Cmd(ADC1, ENABLE);									//ADC1使能
    	
    ...															/*ADC校准*/
    	/*ADC触发*/
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作


    DAC

    stm32F407VET6有两个DAC外设,

    配置过程:

  • 配置GPIO模拟下拉输入

  • 配置DAC模式

  • 设置DAC的值


  • 内存管理

    基本概念

    堆栈(Stack):简称为栈。一种线性表数据结构,是一种只允许在表的一端进行插入和删除操作的线性表。

    核心:

  • 后进先出 (LIFO last in first out)

  • 是一个数据结构的概念

  • 栈区:

    核心:

  • 内存小 大小固定 栈的大小是固定的,由操作系统指定

  • 连续内存地址

  • 编译器自动开辟与释放

  • 存放函数参数值,静态变量

  • 堆区:用于分配程序中动态数据结构的内存空间 核心:

  • 内存大 大小不固定 堆空间通常由系统分配初始大小

  • 不连续内存地址

  • 程序员用malloc释放

  • 易失性存储器(掉电丢失)

    RAM:随机访问存储器

    核心:

  • 临时存储和快速访问

  • 掉电丢失

  • SRAM:静态随机访问存储器

    核心:

  • 读写速度非常快

  • 内存容量小

  • DRAM:动态随机访问存储器

    核心:

  • 读写速度较SRAM慢,但比其他类型的存储器(如硬盘、SSD)快

  • 内存容量大

  • 非易失性存储器(掉电不丢失)

    ROM:只读存储器

    核心:

  • 存储不需要修改的程序和数据

  • FLASH:闪存 核心:

  • 速度通常比RAM慢

  • 有限次多次擦写 几千到几万次之间

  • TF卡,W25Q64属于FLASH

  • EEPROM:带电可擦 可编程只读存储器

    核心:

  • 读写速度:较慢,通常比FLASH慢。

  • 内存小:从几KB到几MB不等,通常用于存储小量数据

  • 擦写次数:有限,但比FLASH多,通常在十万到一百万次之间

  • AT24C02属于EEPROM

  • 汇编基础:

    __align(n)
    
    

    作用:

    用于指定变量或结构体的内存对齐方式。这里的n是一个整数,表示对齐边界,即内存地址应该是n的倍数。

    STM32内存分区:

    C代码内存分布:

    Text段:放程序执行代码的一块内存区域,CPU执行的机器指令

    Data段:静态数据区,存放全局变量、静态变量、常量数据

    BSS段:通常是指用来存放程序中未初始化的全局变量的一块内存区域。属于静态内存分配。

    Heap段:堆,用于存放程序运行中被动态分配的内存段,大小并不固定,可动态扩张或缩减(malloc)。

    Stack段:栈,临时创建的局部变量、用来保存/恢复调用现场、可以把堆栈看成一个寄存、交换临时数据的内存区。

    内存管理意义:

    在单片机中,Malloc()函数会产生内存碎片,自己写内存算法的目的是解决内存碎片这种痛点。

    申请内存


    DCMI


    2.2寸TFT显示屏

    基本知识

    GPIO 配置之ODR, BSRR, BRR

  • ODR寄存器可读可写:既能控制管脚为高电平,也能控制管脚为低电平。管脚对于位写1 gpio 管脚为高电平,写 0 为低电平

  • BSRR 只写寄存器:既能控制管脚为高电平,也能控制管脚为低电平。对寄存器高 16bit 写1 对应管脚为低电平,对寄存器低16bit写1对应管脚为高电平。写 0 ,无动作

  • BRR 只写寄存器:只能改变管脚状态为低电平,对寄存器 管脚对于位写 1 相应管脚会为低电平。写 0 无动作。

  • LCD接口分类

    接口 分辨率 特性
    MCU ≤800*480 带SRAM,无需频繁刷新,无需大内存,驱动简单
    RGB ≤1280*800 不带SRAM,需要实时刷新,需要大内存,驱动稍微复杂
    MIPI 4K 不带SRAM,支持分辨率高,省电,大部分手机屏用此接口

    LCD驱动原理(熟悉)

    1,8080时序,LCD驱动芯片一般使用8080时序控制,实现数据写入/读取

    2,初始化序列(数组),屏厂提供,用于初始化特定屏幕,不同屏幕厂家不完全相同!

    3,画点函数、读点函数(非必需),基于这两个函数可以实现各种绘图功能!

    8080时序

    并口总线时序,常用于MCU屏驱动IC的访问,由Intel提出,也叫英特尔总线

    信号 名称 控制状态 作用
    CS 片选 低电平 选中器件,低电平有效,先选中,后操作
    WR 写信号,上升沿有效,用于数据/命令写入
    RD 读信号,上升沿有效,用于数据/命令读取
    RS 数据/命令 0=命/1=数 表示当前是读写数据还是命令,也叫DC信号
    D[15:0] 数据线 双向数据线,可以写入/读取驱动IC数据

    写数据顺序:

    1. 设置DC电平,写的是数据还是命令

    2. CS引脚拉低 ——> 开始操作

    3. WR拉低,准备写数据

    4. 数据线准备数据 ——> D0~D15赋上对应的值

    5. WR拉高 ——>把数据写入芯片

    6. CS引脚拉高 ——> 结束操作

    7. 释放DC电平,退出操作 (感觉DC是WR/RD的一个代称)

    读数据顺序

    1. 设置DC电平

    2. CS引脚拉低 ——> 开始操作

    3. RD拉低,准备读数据

    4. 核心步骤:读取寄存器的值给临时变量,在函数结束作为返回值

    5. WR拉高,数据结束

    6. CS引脚拉高 ——> 结束操作

    7. 释放DC电平

    剥洋葱顺去:DC层(数据还是命令)——> 片选层(选择芯片)——> 操作层(把读写部分包住)——> 核心读写步骤

    6条指令即可完成对LCD的基本使用(以9341为例)

    指令(HEX) 名称 作用
    0XD3 读ID 用于读取LCD控制器的ID,区分型号用
    0X36 访问控制 设置GRAM读写方向,控制显示方向
    0X2A 列地址 一般用于设置X坐标
    0X2B 页地址 一般用于设置Y坐标
    0X2C 写GRAM 用于往LCD写GRAM数据
    0X2E 读GRAM 用于读取LCD的GRAM数据


    摄像头

    SCCB通信协议(类似IIC)

    SCCB(Serial Camera Control Bus)串行摄像头控制总线,由两根线组成:

  • SIO_C(OV_SCL) 用于传输时钟信号

  • SIO_D(OV_SDA)用于传输数据信号

  • 起始信号:下降沿

    停止信号:上升沿

    有效信号:高电平

    传输过程:

    SCL低电平 ——> 按周期产生高电平信号 ——> 在高电平采集数据

    SDA高电平 ——> 产生下降沿开始信号 ——> 在SCL高电平中给出数据 ——> 在第9个周期有一个din‘t位 ——> 在第10个周期产生上升沿停止信号

    特点:

  • 三相传输周期

  • 有don’t位

  • 写时序:

    ID Address X SUB-Address X Write Date X
    设备写通信地址 内存地址 所写数据

    X:Don’t 位和NA 可写入1或0,对通讯无影响

    读时序:

    ID Address X SUB-Address X ID Address X Read Data X
    设备写通信地址

    注意:不支持连续读写

    集成有源晶振12M,无需外部提供时钟

    FIFO(first in first out)

    本质:对读写寄存器进行了一个扩展

    更本质:就是一种先进先出的数据结构

    解释:

    1. 典型串口 读写缓冲寄存器只有一个字节,在发送完一字节数据后,提醒CPU,存入下一字节的数据,然后继续发送下一字节。

    2. 缺点:每发送一字节数据,都要打断一次CPU,在有操作系统的结构中,还需进行上下文切换,浪费资源

    3. 做一个扩展,一个存取多个字节(64字节)的数据,全部发送完之后再来提醒CPU存入下一批数据


    DCMI(Digital camera interface数字摄像头接口)

    信号名称 信号说明
    D[0:13] 数据(不同数据接口,D有效引脚不同)
    PIXCLK 像素时钟(可设置极性)
    HSYNC 水平同步/数据有效(行同步信号)
    VSYNC 垂直同步(帧同步信号)
    void DCMI_DeInit(void);
    
    /* Initialization and Configuration functions *********************************/
    void DCMI_Init(DCMI_InitTypeDef* DCMI_InitStruct);
    void DCMI_StructInit(DCMI_InitTypeDef* DCMI_InitStruct);
    void DCMI_CROPConfig(DCMI_CROPInitTypeDef* DCMI_CROPInitStruct);
    void DCMI_CROPCmd(FunctionalState NewState);
    void DCMI_SetEmbeddedSynchroCodes(DCMI_CodesInitTypeDef* DCMI_CodesInitStruct);
    void DCMI_JPEGCmd(FunctionalState NewState);
    
    /* Image capture functions ****************************************************/
    void DCMI_Cmd(FunctionalState NewState);
    void DCMI_CaptureCmd(FunctionalState NewState);
    uint32_t DCMI_ReadData(void);
    
    /* Interrupts and flags management functions **********************************/
    void DCMI_ITConfig(uint16_t DCMI_IT, FunctionalState NewState);
    FlagStatus DCMI_GetFlagStatus(uint16_t DCMI_FLAG);
    void DCMI_ClearFlag(uint16_t DCMI_FLAG);
    ITStatus DCMI_GetITStatus(uint16_t DCMI_IT);
    void DCMI_ClearITPendingBit(uint16_t DCMI_IT);


    引脚解读:

  • PIXCLK : 作为信号线 周期的高电平信号时采集 数据

  • HSYNC :高电平有效 即上升沿代表行数据开始传输 在保持高电平时 即是传输此行数据的时间 传输完了恢复低电平(无效)

  • VSYNC :高电平有效 即上升沿代表一帧开始传输 在保持高电平时 即是传输此帧数据的时间 传输完了恢复低电平(无效)

  • OV2640

    引脚:

    引脚 作用 引脚 作用
    D0 数据传输 SCL 时钟线 使用片上I2C外设与它通讯 外设SCL PB10
    D1 数据传输 SDA 数据线 外设SCL PB11
    D2 数据传输 VSYNC 帧同步信号
    D3 数据传输 HREF 行同步信号
    D4 数据传输 PSCK 像素时钟信号,一个PCLK时钟,输出一个(或半个)像素
    D5 数据传输 PWDN 掉电/省电模式,高电平有效
    D6 数据传输 RET 系统复位管脚,低电平有效 随便一个就行
    D7 数据传输 3.3
    NC GND

    使用步骤

    一、初始化引脚

  • D0~D7 上拉 上拉输入

  • RET 系统复位管脚 推挽输出

  • PWDN 掉电/省电模式 推挽输出

  • VSYNC 上拉输入 帧同步信号

  • HREF 上拉输入 行同步信号

  • PSCK 上拉输入 像素时钟信号

    1. 开启重映射

    2. 拉高 PWDN 开启掉电/省电模式

    3. 复位OV2640 拉低RET10ms 再拉高

    二、SCCB配置

  • SCL 开漏输出 时钟信号

  • SDA 上拉输入 数据接受

    1. 操作sensor寄存器

    2. 软复位OV2640

    3. 读取厂家ID

    4. 初始化 OV2640,采用SXGA分辨率(1600*1200)

    5. 执行初始化序列

    #include <stdio.h>
    #include <stm32f1xx.h>
    #include <string.h>
    #include "common.h"
    #include "dcmi_ov2640.h"					//需要DCMI功能
     
    #define STATE_START 0x01
    #define STATE_STOP 0x02
     
    CRC_HandleTypeDef hcrc;
    I2C_HandleTypeDef hi2c1;
    static uint8_t image_buffer[63716]; 		 // 如果存储空间不够了, 把这个数组改小就行了
    static uint8_t image_state; 			    // 图像捕获状态
    static uint32_t image_size; 				// 图像的大小
     
    /* 初始化摄像头 */
    static void camera_init(void)
    {
      GPIO_InitTypeDef gpio;
      LL_DMA_InitTypeDef dma;
      LL_TIM_InitTypeDef tim;
      LL_TIM_IC_InitTypeDef tim_ic;
      LL_TIM_OC_InitTypeDef tim_oc;
      
      __HAL_RCC_AFIO_CLK_ENABLE();
      __HAL_AFIO_REMAP_I2C1_ENABLE();
      __HAL_AFIO_REMAP_TIM3_ENABLE();
      
      __HAL_RCC_CRC_CLK_ENABLE();
      __HAL_RCC_DMA1_CLK_ENABLE();
      __HAL_RCC_GPIOB_CLK_ENABLE();
      __HAL_RCC_GPIOC_CLK_ENABLE();
      __HAL_RCC_GPIOE_CLK_ENABLE();
      __HAL_RCC_I2C1_CLK_ENABLE();
      __HAL_RCC_TIM3_CLK_ENABLE();
      
      hcrc.Instance = CRC;
      HAL_CRC_Init(&hcrc);
      
      // PB8~9连接摄像头的I2C接口, 设为复用开漏输出
      gpio.Mode = GPIO_MODE_AF_OD;
      gpio.Pin = GPIO_PIN_8 | GPIO_PIN_9;
      gpio.Speed = GPIO_SPEED_FREQ_HIGH;
      HAL_GPIO_Init(GPIOB, &gpio);
      
      // PC3(VSYNC)和PC6(=~(HREF & PCLK))为浮空输入
      // PE0~7是摄像头的8位数据引脚, 为浮空输入
      
      // PC5为OV2640的复位引脚, 低电平复位
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_RESET);
      gpio.Mode = GPIO_MODE_OUTPUT_PP;
      gpio.Pin = GPIO_PIN_5;
      gpio.Speed = GPIO_SPEED_FREQ_HIGH;
      HAL_GPIO_Init(GPIOC, &gpio);
      
      // PC7(XCLK)是OV2640时钟, 由TIM3_CH2提供
      // 微雪OV2640摄像头的RET和PWDN引脚默认情况下是没有接到摄像头上的, 无法使用, 这是因为背面有两个0R电阻没有焊
      gpio.Mode = GPIO_MODE_AF_PP;
      gpio.Pin = GPIO_PIN_7;
      gpio.Speed = GPIO_SPEED_FREQ_HIGH;
      HAL_GPIO_Init(GPIOC, &gpio);
      // 建议把这两个0R电阻焊上去, 使RET和PWDN两个引脚可用
      // 否则摄像头一旦死机, 读写不了寄存器了, 就只能板子重新上电才能恢复了
      
      LL_TIM_StructInit(&tim);
      tim.Autoreload = 2; // 72MHz/(2+1)=24MHz
      tim.Prescaler = 0; // 不分频
      LL_TIM_Init(TIM3, &tim);
      
      LL_TIM_OC_StructInit(&tim_oc);
      tim_oc.CompareValue = 1; // 决定占空比
      tim_oc.OCMode = LL_TIM_OCMODE_PWM2;
      tim_oc.OCState = LL_TIM_OCSTATE_ENABLE;
      LL_TIM_OC_Init(TIM3, LL_TIM_CHANNEL_CH2, &tim_oc);
      LL_TIM_EnableCounter(TIM3); // 打开定时器, 开始输出24MHz XCLK时钟
      HAL_Delay(10);
      HAL_GPIO_WritePin(GPIOC, GPIO_PIN_5, GPIO_PIN_SET); // 有了XCLK时钟, 才撤销OV2640复位信号
      
      hi2c1.Instance = I2C1;
      hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
      hi2c1.Init.ClockSpeed = 10000; // 速率: 10kHz (不可太高, 否则会导致Ack Failure)
      HAL_I2C_Init(&hi2c1);
      
      // 每当PC6上出现下降沿时, 就发送一次DMA请求, 采集GPIOC低8位的数据
      LL_TIM_IC_StructInit(&tim_ic);
      tim_ic.ICActiveInput = LL_TIM_ACTIVEINPUT_DIRECTTI; // PC6(TIM3_CH1)映射到TIM3_CH1上
      tim_ic.ICPolarity = LL_TIM_IC_POLARITY_FALLING; // 下降沿触发
      LL_TIM_IC_Init(TIM3, LL_TIM_CHANNEL_CH1, &tim_ic);
      // 无需让定时器3开始计时, 这里只使用该定时器的一个输入捕获通道
      
      // 配置TIM3_CH1对应的DMA通道
      dma.Direction = LL_DMA_DIRECTION_PERIPH_TO_MEMORY;
      dma.MemoryOrM2MDstAddress = (uint32_t)image_buffer;
      dma.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
      dma.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
      dma.Mode = LL_DMA_MODE_NORMAL;
      dma.NbData = sizeof(image_buffer); // 采集的最大图像大小, 超出部分会被自动丢弃!!
      dma.PeriphOrM2MSrcAddress = (uint32_t)&GPIOE->IDR;
      dma.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_BYTE;
      dma.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
      dma.Priority = LL_DMA_PRIORITY_VERYHIGH;
      LL_DMA_Init(DMA1, LL_DMA_CHANNEL_6, &dma);
      LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_6);
      
      OV2640_Init(JPEG_800x600);
      
      // 打开PC3外部中断
      LL_GPIO_AF_SetEXTISource(LL_GPIO_AF_EXTI_PORTC, LL_GPIO_AF_EXTI_LINE3);
      LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_3); // PC3上的上升沿能触发中断
      LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_3); // PC3上的下降沿也能触发中断
      LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_3);
      HAL_NVIC_EnableIRQ(EXTI3_IRQn); // 允许执行中断服务函数
    }
     
    /* 向串口发送图像数据, 并在末尾附上CRC校验码 */
    static void dump(const void *data, uint32_t size)
    {
      uint8_t value;
      uint32_t i;
      uint32_t temp;
      
      __HAL_CRC_DR_RESET(&hcrc);
      for (i = 0; i < size; i++)
      {
        // 输出图像数据
        value = *((uint8_t *)data + i);
        printf("%02X", value);
        
        // 每4字节计算一次CRC
        if ((i & 3) == 0)
        {
          if (i + 4 <= size)
            temp = HAL_CRC_Accumulate(&hcrc, (uint32_t *)((uint8_t *)data + i), 1);
          else
          {
            temp = 0;
            memcpy(&temp, (uint8_t *)data + i, size - i);
            temp = HAL_CRC_Accumulate(&hcrc, &temp, 1);
          }
        }
      }
      
      // 输出CRC
      temp = (temp >> 24) | ((temp >> 8) & 0xff00) | ((temp & 0xff00) << 8) | ((temp & 0x00ff) << 24);
      printf("%08X\n", temp);
    }
     
    int main(void)
    {
      HAL_Init();
      
      clock_init();
      usart_init(115200);
      
      printf("STM32F107VC OV2640\n");
      printf("SystemCoreClock=%u\n", SystemCoreClock);
      
      camera_init();
      
      while (1)
      {
        if (image_state == (STATE_START | STATE_STOP))
        {
          printf("size=%d\n", image_size);
          dump(image_buffer, image_size); // 通过串口发送图像, 然后附上CRC校验值
          
          // 让DMA内部指针回到数组的开头
          LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_6);
          LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_6, sizeof(image_buffer));
          LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_6);
          
          image_state = 0; // 允许采集新图像 (这条语句一次性把START和STOP都清0了)
        }
      }
    }
     
    void EXTI3_IRQHandler(void)
    {
      LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3); // 清除中断标志位
      if (LL_GPIO_IsInputPinSet(GPIOC, LL_GPIO_PIN_3))
      {
        // PC3上升沿表示图像数据传输开始
        if (image_state != 0)
          return; // 如果图像已经开始采集了, 就忽略这个开始信号
        image_state = STATE_START;
        
        // 打开TIM3_CH1对应的DMA通道, 开始采集数据
        LL_TIM_EnableDMAReq_CC1(TIM3); // 允许PC6上的下降沿触发DMA请求
      }
      else
      {
        // PC3下降沿表示图像数据传输结束
        if ((image_state & STATE_START) == 0 || (image_state & STATE_STOP))
          return; // 忽略没有开始信号的结束信号, 以及重复的结束信号
        image_state |= STATE_STOP;
        
        LL_TIM_DisableDMAReq_CC1(TIM3); // 停止采集
        image_size = sizeof(image_buffer) - LL_DMA_GetDataLength(DMA1, LL_DMA_CHANNEL_6); // 总量-剩余数据量=图像大小
      }
    }

    1,配置DCMI接口

    
    


    ESP8266

    本质:串口转WIFI

    引脚:

  • RX:接TX

  • TX:接RX

  • 3v3:接3.3V电压

  • GND:接地

  • EN:接3.3V电压

  • RST:接3.3V电压

  • 核心:发送数据寄存器,

    模式:

  • AP模式:相当于路由器

  • STA模式:相当于电脑,需要连路由器

  • STA+AP 模式:两种模式的共存模式

  • AP:也就是无线接入点,是一个无线网络的创建者,是网络的中心节点。一般家庭或办公室使用的无线路由器就是一个AP。

    STA:每一个连接到无线网络中的终端(如笔记本电脑、PDA及其它可以联网的用户设备)都可称为一个站点。

    AT指令:

    基础部分:

  • AT:测试能不能正常链接

  • AT+RST:重启模块

  • AT+GMR:查看版本信息

  • 2、ATCWMODE=<mode>

    功能:

  • mode=1 : Station模式(接收模式)

  • mode=2:AP模式(发送模式)

  • mode=3:AP+Station模式

  • 3、AT+ CWSAP= <ssid>,<pwd>,<chl>, <ecn>

    功能:配置AP参数(指令只有在AP模式开启后有效)

  • ssid:接入点名称

  • pwd:密码

  • chl:通道号

  • ecn:加密方式:(0-OPEN, 1-WEP, 2-WPA_PSK, 3-WPA2_PSK, 4-WPA_WPA2_PSK)

  • 注意:此设置完成后,连接网络会可能出现连接不上的情况,请发送 AT+RST 命令并等待几分钟之 后再连接。

    4、AT+CWLIF

     功能:查看已接入设备的 IP

    5、AT+CIFSR

    功能:查看本模块的 IP 地址
    
    注意: AP 模式下无效!会造成死机现象!

    6、AT+CWMODE?

    功能:查看本机配置模式

    7、AT+CIPMUX?

    功能:查询本模块是否建立多连接
    
    说明: <mode>:0-单路连接模式, 1-多路连接模式

    8、AT+CIPMODE? 功能:查询本模块的传输模式

    说明: <mode>:0-非透传模式, 1-透传模式

    9、AT+CIPSTO?

    功能:查询本模块的服务器超时时间

    10、AT+CIPMUX=1

    功能:开启多连接模式

    11、AT+CIPSERVER=1,8080

    功能:创建服务器

    关闭 server 服务如下图所示:

    说明: <mode>:0-关闭 server 模式, 1-开启 server 模式 <port>:端口号,缺省值为 333

    说明: (1) AT+ CIPMUX=1 时才能开启服务器;关闭 server 模式需要重启 (2)开启 server 后自动建立 server 监听,当有 client 接入会自动按顺序占用一个连 接。

    12、AT+CIPSTO=2880

        功能:设置服务器超时时间

    13、AT+CIPSTATUS

        功能:查看当前连接

    说明: <id>:连接的 id 号 0-4 <type>:字符串参数,类型 TCP 或 UDP <addr>:字符串参数, IP 地址 <port>:端口号 <tetype>: 0-本模块做 client 的连接, 1-本模块做 server 的连接

    14、AT+CIPSEND=1,6

        功能:向某个连接发送数据

    指令: 1)单路连接时(+CIPMUX=0),指令为: AT+CIPSEND=<length> 2)多路连接时(+CIPMUX=1) ,指令为: AT+CIPSEND= <id>,<length> 响应:收到此命令后先换行返回”>”,然后开始接收串口数据 当数据长度满 length 时发送数据。 如果未建立连接或连接被断开,返回 ERROR 如果数据发送成功,返回 SEND OK 说明: <id>:需要用于传输连接的 id 号 <length>:数字参数,表明发送数据的长度,最大长度为 2048

    15、AT+CIPSERVER=0 功能:关闭 server 服务

    指令: AT+CIPSERVER=<mode>[,<port>] 说明: <mode>:0-关闭 server 模式, 1-开启 server 模式 <port>:端口号,缺省值为 333 响应: OK 说明: (1) AT+ CIPMUX=1 时才能开启服务器;关闭 server 模式需要重启 (2)开启 server 后自动建立 server 监听,当有 client 接入会自动按顺序占用一个连 接。

    16、AT+CIPSTART=2,"TCP","192.168.4.101",8080 功能:建立 TCP 连接

    指令: 1)单路连接时(+CIPMUX=0),指令为: AT+CIPSTART= <type>,<addr>,<port> 2)多路连接时(+CIPMUX=1),指令为: AT+CIPSTART=<id>,<type>,<addr>,<port> 响应:如果格式正确且连接成功,返回 OK,否则返回 ERROR 如果连接已经存在,返回 ALREAY CONNECT 说明: <id>:0-4,连接的 id 号 <type>:字符串参数,表明连接类型, ”TCP”-建立 tcp 连接, ”UDP”-建立 UDP 连接 <addr>:字符串参数,远程服务器 IP 地址 <port>:远程服务器端口号

    17、AT+CIPSEND=2,8

    指令: 1)单路连接时(+CIPMUX=0),指令为: AT+CIPSEND=<length> 2)多路连接时(+CIPMUX=1) ,指令为: AT+CIPSEND= <id>,<length> 响应:收到此命令后先换行返回”>”,然后开始接收串口数据 当数据长度满 length 时发送数据。 如果未建立连接或连接被断开,返回 ERROR 如果数据发送成功,返回 SEND OK 说明: <id>:需要用于传输连接的 id 号 <length>:数字参数,表明发送数据的长度,最大长度为 2048 18、AT+CWLAP

        功能:查看当前无线路由器列表

    响应:正确: (终端返回AP列表)

  • CWLAP: <ecn>,<ssid>,<rssi> OK 错误: ERROR 说明: < ecn >:0-OPEN, 1-WEP, 2-WPA_PSK, 3-WPA2_PSK, 4-WPA_WPA2_PSK <ssid>:字符串参数,接入点名称 <rssi>:信号强度 19、AT+CWJAP=”MERSAIN”,”XXXXXXXX”

     功能:加入当前无线网络
  • 指令: AT+CWJAP=<ssid>,< pwd > 说明: <ssid>:字符串参数,接入点名称 <pwd>:字符串参数,密码,最长64字节ASCII 响应:正确: OK 错误: ERROR 20、AT+CWJAP?

        功能:检测是否真的连上该路线网络

    指令: AT+CWJAP? 响应:返回当前选择的AP

  • CWJAP:<ssid> OK 说明: <ssid>:字符串参数,接入点名称 21、AT+CIFSR

      功能:查看模块 IP 地址
  • 指令: AT+CIFSR 响应:正确: + CIFSR:<IP address> OK 错误: ERROR 说明: <ssid>:字符串参数,接入点名称


    NRF24L01

    简介:可以实现点对点或是 1 对 6 的无线通信。 nrf24l01主要用于短距离无线通信,esp8266则主要用于连接互联网。

    需要知识基础:stm32单片机、软件模拟SPI通信

    PS:本文为了通俗易懂便于理解,可能会失一定专业性,有错误或其他高见欢迎评论区讨论

    引脚:

  • MOSI:主器件数据输出,从器件数据输入

  • MISO:主器件数据输入,从器件数据输出

  • SCLK:时钟信号,其上升沿或下降沿是数据移入或移出的条件

  • CSN :从器件使能信号(片选线)拉低时,NRF响应SPI通信, 拉高时,NRF结束响应SPI通信

  • CE:CE 协同CONFIG 寄存器共同决定NRF24L01 的状态

  • IRQ:中断信号线,中断输出。低电平有效,中断时变为低电平,在以下三种情况变低:Tx FIFO 发完并且收到ACK(使能ACK情况下)、Rx FIFO收到数据、达到最大重发次数。

  • VCC:电压范围1.9V~3.6V,超过3.6V将会烧毁模块。一般电压3.3V左右。除电源VCC和接地端,其余脚都可以直接和普通的5V单片机IO口直接相连,无需电平转换。

  • GND:地

  • 模式:

  • Power Down Mode:掉电模式

  • Tx Mode:发射模式

  • Rx Mode:接收模式

  • Standby-1Mode:待机 1 模式

  • Standby-2 Mode:待机 2 模式

  • 待机模式 待机模式 I 在保证快速启动的同时减少系统平均消耗电流。在待机模式 I 下,晶振正常工作。在待机模式 II 下部分时钟缓冲器处在工作模式。当发送端 TX FIFO 寄存器为空并且 CE 为高电平时进入待机模式 II。在待机模式期间,寄存器配置字内容保持不变。

    掉电模式 在掉电模式下,nRF20L01 各功能关闭,保持电流消耗最小。进入掉电模式后,nRF24L01 停止工作,但寄存器内容保持不变。掉电模式由寄存器 PWR_UP 位来控制。

    接收模式

    接收模式下可以接收6路不同通道的数据,每个数据通道使用不同的地址,但是共用相同的频道。也就是说6个不同的 nRF24L01 设置为发送模式后可以与用一个设置为接收模式的 nRF24L01 进行通讯,而设置为接收模式的 nRF24L01 可以对这个6个发送端进行识别。数据通道0是唯一的一个可以配置为 40 位自身地址的数据通道。1~5数据通道都为8位自身地址和32位公用地址。所有的数据通道都可以设置为增强型 ShockBurst 模式。 nRF24L01 在确认收到数据后记录地址,并以此地址为目标地址发送应答信号。在发送端,数据通道0被用作接收应答信号,因此,数据通道0的接收地址要与发送端地址相等以确保接收到正确的应到信号。

    工作过程


    按键

    基础知识

    下文中a为二进制数(0或1)

    与(&)

  • 如果两个对应位都是1,结果为1。

  • 如果两个对应位有一个不是1,结果为0。

  • a^1 = a

  • a^0 = 0

  • 或(|)

  • 如果两个对应位都是0,结果为0。

  • 如果两个对应位有一个是1,结果为1。

  • a | 1 = 1

  • a | 0 = a

  • 非(|)

  • 对变量自身作用,取反

  • 优先级最强,在运算中最先运算

  • 异或(^)

  • 如果两个对应位相同,结果为0。

  • 如果两个对应位不同,结果为1。

  • a^1 = ~a 与1异或相当于取反

  • a^0 = a 与0异或相当于不变

  • 优先级(从高到低):

    1. 按位取反:~

    2. 左移位和右移位:<<>>

    3. 按位与:&

    4. 按位异或:^

    5. 按位或:|

    状态机代码解析

    Key_Val = Key_GetNum();//实时读取键码值
    Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿
    Key_Up = ~Key_Val & (Key_Old ^ Key_Val);//捕捉按键上降沿
    Key_Old = Key_Val;//辅助扫描变量
    switch(Key_Down)
    {
    	...
    }
    变量值 未按下 刚按下 完全按下 刚抬起 完全抬起
    Key_Val 0 1 1 0 0
    第二行代码的Key_Old 0 0 1 1 0
    Key_Old ^ Key_Val 0 1 0 1 0
    Key_Down 0 1 0 0 0
    Key_Up 0 0 0 1 0
    第四行代码的Key_Old 0 1 0 0 0

    很显然,上述四行代码的意思是:

  • 当轮询时,上一次Key_Val读取为0,这次Key_Val读取为1,视为按下了一次,这次Key_Down为1

  • 当按键完全按下后,即连续两次Key_Val被读取为1之后,Key_Val读取为0,视作抬起一次这次Key_Up为1

  • 当进去完全按下状态后,连续两次检测为0,则可以回到抬起状态

  • 那么,是如何实现消抖的呢?看真值表就知道了

    变量值 刚按下 抖动
    Key_Val 1 0
    第二行代码的Key_Old 0 1
    Key_Old ^ Key_Val 1 1
    Key_Down 1 0
    Key_Up 0 1
    第四行代码的Key_Old 1 0
    变量值 刚抬起 抖动
    Key_Val 0 1
    第二行代码的Key_Old 1 0
    Key_Old ^ Key_Val 1 1
    Key_Down 0 1
    Key_Up 1 0
    第四行代码的Key_Old 0 1

    重点关注 Key_Down 与 Key_Up的值

    正常按键状态顺序:

    
    

    完全抬起

    刚按下

    完全按下

    刚抬起

    完全抬起

    总结:

    完全抬起到刚按下需要一次Key_Val = 1;

    刚按下到完全按下需要一次Key_Val = 1;

    完全按下到刚抬起需要一次Key_Val = 0;

    刚抬起到完全抬起需要一次Key_Val = 0;

    若刚按下状态后Key_Val = 0,视作抬起一次(显然不合理,还没进去完全按下,却进去抬起状态了)

    若刚抬起状态后Key_Val = 1,视作放下一次(显然不合理,还没进去完全抬起,却又按了一次)


    PID算法


    C/C++基础

    命名空间:

    作用:函数名、变量名被框在一个局部空间内作用,防止,重名

    性质:

  • 可以不连续

  • 可嵌套

  • 语法:

    //空间名字:zhangsan
    //成员:a,c,Swap
    namespace zhangsan
    {
    	//声明或定义(命名空间成员)
    	int a;
        char c;
        void Swap(int* p1, int* p2);
    }
    
    //声明语句 引用std空间里的成员cout 进全局作用域(全局命名空间)
    using std::cout;
    //指示语句 引用std空间里的所有成员 进全局作用域(全局命名空间)
    using namespace std;
    //不直接引用,使用时引用一下
    std::cout << "hello C++" << std::endl;
    

    输入输出

    int a
    std::cin >> a;		//相当于 scanf("",&a);
    cout << a << endl;	//相当于 printf("%d",a);

    函数

    //函数缺省(类似python)
    int func(int a = 1)
    {
    	return a;
    }
    //函数重载(参数个数重载,参数类型重载,参数顺序重载,)
    int Add(int a, int b)
    {
    	return a + b;
    }
    double Add(double a, double b)
    {
    	return a + b;
    }
     
    int main()
    {
    	Add(1, 3); // 调用第一个Add函数
    	Add(2.6, 5.7);  // 调用第二个Add函数
    	return 0;
    }
    

    new运算符

    作用:动态申请存储空间的运算符。

    语法:

    //使用new申请一个对象
    int *p = new int(10);//申请了一个初值为10的整型数据
    //使用new申请数组
    int *arr = new int[10];//申请了能存放10个整型数据元素的数组,其首地址为arr

    new+数据类型(初值),返回值为申请空间的对应数据类型的地址。

    delete运算符

    new运算符通常搭配delete元素安抚来使用,new用来动态申请存储空间,delete用于释放new申请的空间。 语法格式如下:

    delete p;
    delete[] arr;//注意要删除数组时,需要加[],以表示arr为数组。

    new和malloc的区别: 熟悉c语言的都知道,c语言的头文件<malloc.h>中也提供了类似的函数用于动态申请存储空间。那么到底有哪些细节上的差异呢,让我们来细究一下。

  • new是一个运算符,在标准库函数中就有,不需要导入头文件。malloc函数封装在头文件<malloc.h>中,要使用必须先导入头文件。

  • 使用malloc函数,必须用sizeof运算符给出申请空间的大小。new运算符则会自动计算出所需要申请空间的大小。

  • malloc返回值通常需要进行强制类型转化。new运算符则可以直接返回对应数据类型的地址。


  • openmv

    00

    Lab是由一个亮度通道(channel)和两个颜色通道组成的。在Lab颜色空间中,每个颜色用L、a、b三个数字表示,各个分量的含义是这样的: – L**代表亮度* – **a*代表从绿色到红色的分量 – b**代表从蓝色到黄色**的分量

    01 拍摄图像到IDE

     import sensor, image, time
     ​
     #五行固定代码
     sensor.reset()
     sensor.set_pixformat(sensor.RGB565)
     sensor.set_framesize(sensor.QVGA)
     sensor.skip_frames(time = 2000)
     clock = time.clock()
     ​
     while(True):
         clock.tick()            #初始化时钟对象
         img = sensor.snapshot()  #拍摄一张照片
         print(clock.fps())       #获得拍摄图像的帧率
     ​

    02 像素统计

     sensor.reset()                      # 初始化摄像头
     sensor.set_pixformat(sensor.RGB565)   # 格式为 RGB565.
     sensor.set_framesize(sensor.QVGA)
     sensor.skip_frames(10)              # 跳过10帧,使新设置生效
     sensor.set_auto_whitebal(False)       #关闭白平衡
     ​
     ROI=(140,100,40,40)         #左上角坐标 宽度 高度
     Red = (255,0,0)
     while(True):
         img = sensor.snapshot()                 #拍摄一张照片
         img.draw_rectangle(ROI, color=Red)#接下来要统计的区域 画上红色框
         statistics=img.get_statistics(roi=ROI)   #计算ROI区域的统计信息
         color_l=statistics.l_mode()             #获取Lab颜色空间中的L(明度)分量的众数
         color_a=statistics.a_mode()             #获取Lab颜色空间中的A(色度)分量的众数 越大越红绿
         color_b=statistics.b_mode()             #获取Lab颜色空间中的B(色度)分量的众数 越大越黄蓝
         color_RGB = image.lab_to_rgb(color_l,color_a,color_b)   #LAB转RGB
         img.draw_line((0,30,60,30), color=color_RGB,thickness=40)#在左上角显示识别到的颜色
         print(color_l,color_a,color_b)
         img.draw_rectangle(ROI)                 #在捕获的图像上绘制一个矩形框

    03图像上画图

    import sensor, image, time
    
    #五行固定代码
    #...
    
    clock = time.clock()
    line_tuple = (10,10,100,100)	//start_x start_y dir_x dir_y
    rect_tuple = (100,100,50,50)	//start_x start_y width length
    Point_x = 150
    Point_y = 150
    radius = 10
    cross_x = 180
    cross_y = 180
    str_x = 210
    str_y = 210
    text = "12345qwer"
    Red = (255,0,0)		#RGB颜色  (0,0,0)	黑色 (255,255,255)	白色
    while(True):
        clock.tick()
        img = sensor.snapshot()
        img.draw_line(line_tuple, color=Red)
        img.draw_rectangle(rect_tuple, color=Red)
        img.draw_circle(Point_x, Point_y, radius, color=Red)
        img.draw_cross(cross_x, cross_y, size=5, color=Red)
        img.draw_string(str_x, str_y, text, color=Red)
        print(clock.fps())
    

    004 找色块并画出

    import sensor
    import time
    
    #五行固定代码
    #...
    red = (0, 100, 18, 127, 7, 127)		#红色阈值,用工具调整
    
    while True:
        clock.tick()  
        img = sensor.snapshot()  
        for blobs in img.find_blobs([red]):		# 该方法返回的是列表
            img.draw_rectangle(blobs.rect())
        print(clock.fps())  

    005串口通信

    #关键代码
    from machine import UART 	#引用UART库
    #...
    uart = UART(3, 19200)		#初始化串口3
    uart.write("Hello World!\r") #写函数

    006NCC模板匹配 很拉

    import time
    import sensor
    import image
    from image import SEARCH_EX
    
    sensor.reset()		# Set sensor settings
    sensor.set_contrast(1)
    sensor.set_gainceiling(16)			# Max resolution for template matching with SEARCH_EX is QQVGA
    sensor.set_framesize(sensor.QQVGA)	# You can set windowing to reduce the search image.
    # sensor.set_windowing(((640-80)//2, (480-60)//2, 80, 60))
    sensor.set_pixformat(sensor.GRAYSCALE)
    
    # Template should be a small (eg. 32x32 pixels) grayscale image.
    template1 = image.Image("/1.pgm")
    template2 = image.Image("/1.pgm")
    template3 = image.Image("/1.pgm")
    clock = time.clock()
    
    while True:
        clock.tick()
        img = sensor.snapshot()	# find_template(template, threshold, [roi, step, search])
        # ROI: The region of interest tuple (x, y, w, h).
        # Step: The loop step used (y+=step, x+=step) use a bigger step to make it faster.
        # Search is either image.SEARCH_EX for exhaustive search or image.SEARCH_DS for diamond search
        # Note1: ROI has to be smaller than the image and bigger than the template.
        # Note2: In diamond search, step and ROI are both ignored.
        r = img.find_template(template1, 0.70, step=4, search=SEARCH_EX)  #roi=(10, 0, 60, 60))
        if r:
            img.draw_rectangle(r)
            
        r = img.find_template(template2, 0.70, step=4, search=SEARCH_EX)  #roi=(10, 0, 60, 60))
        if r:
            img.draw_rectangle(r)
        
        r = img.find_template(template3, 0.70, step=4, search=SEARCH_EX)  #roi=(10, 0, 60, 60))
        if r:
            img.draw_rectangle(r)
        
        print(clock.fps())

    007

    
    

    ##

    作者:在下马大胆

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32万字学习笔记

    发表回复