STM32万字学习笔记

前言:无意发现之前学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、2561、2、4、8
计数器12位6位(有效计数)
超时时间0.1ms~26214.4ms113us~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,需要实时刷新,需要大内存,驱动稍微复杂
MIPI4K不带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 AddressXSUB-AddressXWrite DateX
设备写通信地址内存地址所写数据

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

读时序:

ID AddressXSUB-AddressXID AddressXRead DataX
设备写通信地址

注意:不支持连续读写

集成有源晶振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外设与它通讯 外设SCLPB10
D1数据传输SDA数据线 外设SCLPB11
D2数据传输VSYNC帧同步信号
D3数据传输HREF行同步信号
D4数据传输PSCK像素时钟信号,一个PCLK时钟,输出一个(或半个)像素
D5数据传输PWDN掉电/省电模式,高电平有效
D6数据传输RET系统复位管脚,低电平有效 随便一个就行
D7数据传输3.3
NCGND

使用步骤

一、初始化引脚

  • 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_Val01100
第二行代码的Key_Old00110
Key_Old ^ Key_Val01010
Key_Down01000
Key_Up00010
第四行代码的Key_Old01000

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

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

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

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

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

变量值刚按下抖动
Key_Val10
第二行代码的Key_Old01
Key_Old ^ Key_Val11
Key_Down10
Key_Up01
第四行代码的Key_Old10

变量值刚抬起抖动
Key_Val01
第二行代码的Key_Old10
Key_Old ^ Key_Val11
Key_Down01
Key_Up10
第四行代码的Key_Old01

重点关注 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

 

##

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值