前言:无意发现之前学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通信核心:交换一个字节
-
准备好写的数据 0或1
-
把SCK拉高
-
瞅一眼接受的是0还是1
-
把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为了省电默认关闭,需要手动开启
配置流程:
-
启PWR,BKP时钟使能RTC的访问(配置时钟需要访问寄存器)
-
启动时钟并选择时钟
-
配置预分频器使得时钟为1HZ
-
写入CNT,相当于给一个初始时间
-
选择配置中断(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数据 |
写数据顺序:
-
设置DC电平,写的是数据还是命令
-
CS引脚拉低 ——> 开始操作
-
WR拉低,准备写数据
-
数据线准备数据 ——> D0~D15赋上对应的值
-
WR拉高 ——>把数据写入芯片
-
CS引脚拉高 ——> 结束操作
-
释放DC电平,退出操作 (感觉DC是WR/RD的一个代称)
读数据顺序
-
设置DC电平
-
CS引脚拉低 ——> 开始操作
-
RD拉低,准备读数据
-
核心步骤:读取寄存器的值给临时变量,在函数结束作为返回值
-
WR拉高,数据结束
-
CS引脚拉高 ——> 结束操作
-
释放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)
本质:对读写寄存器进行了一个扩展
更本质:就是一种先进先出的数据结构
解释:
-
典型串口 读写缓冲寄存器只有一个字节,在发送完一字节数据后,提醒CPU,存入下一字节的数据,然后继续发送下一字节。
-
缺点:每发送一字节数据,都要打断一次CPU,在有操作系统的结构中,还需进行上下文切换,浪费资源
-
做一个扩展,一个存取多个字节(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 上拉输入 像素时钟信号
-
开启重映射
-
拉高 PWDN 开启掉电/省电模式
-
复位OV2640 拉低RET10ms 再拉高
二、SCCB配置
-
SCL 开漏输出 时钟信号
-
SDA 上拉输入 数据接受
-
操作sensor寄存器
-
软复位OV2640
-
读取厂家ID
-
初始化 OV2640,采用SXGA分辨率(1600*1200)
-
执行初始化序列
#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异或相当于不变
优先级(从高到低):
-
按位取反:
~
-
左移位和右移位:
<<
和>>
-
按位与:
&
-
按位异或:
^
-
按位或:
|
状态机代码解析
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
##