STM 32 GPIO 的输入输出模式

在STM 32 的标准库函数中有以下结构体 typedef enum { GPIO_Mode_AIN = 0x0, GPIO_Mode_IN_FLOATING = 0x04, GPIO_Mode_IPD = 0x28, GPIO_Mode_IPU = 0x48, GPIO_Mode_Out_OD = 0x14, GPIO_Mode_Out_PP = 0x10, GPIO_Mode_AF_OD = 0x1C, GPIO_Mode_AF_PP = 0x18 }GPIOMode_TypeDef; 下面对每个模式进行简单的介绍 GPIO_Mode_AIN (模拟输入) 模拟输入是指传统方式的输入,数字输入是输入PCM数字信号,即0,1的二进制数字信号,通过数模转换, 转换成模拟信号,经前级放大进入功率放大器,功率放大器还是模拟的 GPIO_Mode_IN_FLOATING (输入浮空) 浮空就是逻辑器件与引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时, 相当于该引脚接了高电平。一般实际运用时,引脚不建议悬空,易受干扰。通俗讲就是浮空就是浮在空中,就相当于此端口在默认情况下什么都不接,呈高阻态,这种设置在数据传输时用的比较多。浮空最大的特点就是电压的不确定性,它可能是0V,页可能是VCC,还可能是介于两者之间的某个值(最有可能) 浮空一般用来做ADC输入用,这样可以减少上下拉电阻对结果的影响 GPIO_Mode_IPD (输入上拉) 上拉就是把点位拉高,比如拉到Vcc。上拉就是将不确定的信号通过一个电阻嵌位在高电平。电阻同时起到限流的作用。弱强只是上拉电阻的阻值不同,没有什么严格区分 GPIO_Mode_IPU (输入下拉) 就是把电压拉低,拉到GND。与上拉原理相似 GPIO_Mode_Out_OD (开漏输出) 输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内) 开漏形式的电路有以下几个特点: 利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经R pull-up ,MOSFET到GND。IC内部仅需很下的栅极驱动电流。 一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻 决定了逻辑电平转换的沿的速度 。阻 越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。) OPEN-DRAIN提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。 可以将多个开漏输出的Pin,连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系。这也是I2C,SMBus等总线判断总线占用状态的原理。补充:什么是“线与”?: 在一个结点(线)上, 连接一个上拉电阻到电源 VCC 或 VDD 和 n 个 NPN 或 NMOS 晶体管的集电极 C 或漏极 D, 这些晶体管的发射极 E 或源极 S 都接到地线上, 只要有一个晶体管饱和, 这个结点(线)就被拉到地线电平上. 因为这些晶体管的基极注入电流(NPN)或栅极加上高电平(NMOS),晶体管就会饱和, 所以这些基极或栅极对这个结点(线)的关系是或非 NOR 逻辑. 如果这个结点后面加一个反相器, 就是或 OR 逻辑. ...

March 4, 2023 · 1 min · 晚晴

从头写一个实时操作系统(CPU任务切换)(汇编)

实时操作系统(Real Time Operating System,简称RTOS)是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统。提供及时响应和高可靠性是其主要特点。 (来源:百度百科) 实时操作系统要实现的最基本的功能便是任务的切换,与裸机程序不同,在实时操作系统中,每个任务都会拥有其专属的任务堆栈用于任务变量的存储,个人认为操作系统中最难以理解之处就是涉及到汇编部分的CPU现场保存和新任务的切换。 我们这次将在STM32F4(cortex-M4 内核)上简单的实现使用汇编完成任务的切换,首先简单的写一下一下C语言部分的代码,我们需要写两个任务函数(Task1、Task2),这两个函数会在运行中不断的进行切换。 void Task1(){ while(1){ printf("1"); } } void Task2(){ while(1){ printf("2"); } } 除了任务的执行代码外,我们还需要为两个任务初始化堆栈,我们简单的为两个任务风别分配200个32为的空间。 STM32的堆栈地址是从高地址向低地址生长的,即入栈地址减一,因此我们的堆栈地址从我们分配的数组的最后一个元素地址开始即 &TASK1_Stack[199] ,接着我们假设该任务已经被切换出,且此时CPU的所有寄存器都已经保存完毕,我们给他们赋予初值,除了 PS 和 LR 以及PSR 之外 我们可以随意赋值,PS为该任务的入口即Task1 函数的地址,PC该任务结束时返回的地址,我们让任务结束时运行EDN_Handle函数。 static unsigned int TASK1_Stack[200]; static unsigned int TASK2_Stack[200]; void task_init(){ /// 初始化 任务的堆栈 /// STM32 的堆栈是从 高地址 --》 低地址 Task1_stack_p = &TASK1_Stack[200-1]; Task2_stack_p = &TASK2_Stack[200-1]; /// 此时 两个任务的堆栈指针都存在 了 Task_X_stack_p 中 *(--Task1_stack_p) = 0x01000000uL; // 程序状态寄存器 PSR *(--Task1_stack_p) = (unsigned int)Task1; // 程序开始地址 PC *(--Task1_stack_p) = (unsigned int)END_Handle; *(--Task1_stack_p) = 0x00001234uL; // R12 *(--Task1_stack_p) = 0x7788521AuL; // R3 *(--Task1_stack_p) = 0x7788521BuL; // R2 *(--Task1_stack_p) = 0x7788521CuL; // R1 *(--Task1_stack_p) = 0x00000001uL; // R0 *(--Task1_stack_p) = 0x7788521AuL; // R11 *(--Task1_stack_p) = 0x7788521BuL; // R10 *(--Task1_stack_p) = 0x7788521CuL; // R9 *(--Task1_stack_p) = 0x7788521DuL; // R8 *(--Task1_stack_p) = 0x7788521EuL; // R7 *(--Task1_stack_p) = 0x7788521FuL; // R6 *(--Task1_stack_p) = 0x7788521FuL; // R5 *(--Task1_stack_p) = 0x7788521FuL; // R4 实时操作系统要想实现任务的调度,就必须有时间的度量,在一个任务运行一段时间之后切换到下一个该执行的任务,以保证系统的实时性,因此我们必须让系统在指定的时间段进行任务的调度,在STM32中我们可以使用StsTick计数器的中断实现任务的切换,但是在STM32 中硬件为我们提供了另外一个中断(PendSV)顾名思义该中断是可以挂起的,在STM32中一般都会使用PendSv中断去执行任务的中断,因为其可挂起的特性,可以使程序在运行时先执行其他外设所产生的中断,在这些中断执行完毕后再去进行任务的调度,以此保证其他中断能够及时的得到相应,因此我们要在Systick的中断中去开启PendSV中断。以下代码中我们在Systick中断中找到接下来要运行的任务,使用 PENDSV( )函数开启PendSV中断。 ...

February 14, 2023 · 2 min · 晚晴

ZIGBEE温湿度传感器代码分析

传感器介绍 产品详细介绍 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。产品为4针单排引脚封装,连接方便。 典型应用电路 传感器协议 单总线(OneWire)协议介绍 1-wire 单总线是 Maxim 全资子公司 Dallas 的一项专有技术与目前多数标准串行数据通信方式,如 SPI/I2C/MICROWIRE不同,它采用单根信号线,既传输时钟又传输数据,而且数据传输是双向的。它具有节省 I/O口线资源、结构简单、成本低廉、便于总线扩展和维护等诸多优点。 协议图示 代码分析 裸机代码 #include <ioCC2530.h> #define uint unsigned int #define uchar unsigned char #define wenshi P0_6 //温湿度定义 uchar ucharFLAG,uchartemp; uchar shidu_shi,shidu_ge,wendu_shi,wendu_ge=4; uchar ucharT_data_H,ucharT_data_L,ucharRH_data_H,ucharRH_data_L,ucharcheckdata; uchar ucharT_data_H_temp,ucharT_data_L_temp,ucharRH_data_H_temp,ucharRH_data_L_temp,ucharcheckdata_temp; uchar ucharcomdata; uchar temp[2]={0,0}; uchar temp1[5]="temp="; uchar humidity[2]={0,0}; uchar humidity1[9]="humidity="; /**************************** 延时函数 *****************************/ void Delay_us() //1 us延时 { asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); } void Delay_10us() //10 us延时 { Delay_us(); Delay_us(); Delay_us(); Delay_us(); Delay_us(); Delay_us(); Delay_us(); Delay_us(); Delay_us(); Delay_us(); } void Delay_ms(uint Time)//n ms延时 { unsigned char i; while(Time--) { for(i=0;i<100;i++) Delay_10us(); } } /*********************** 温湿度传感 ***********************/ void COM(void) // 温湿写入 { uchar i; for(i=0;i<8;i++) /// for 循环 8 次 每次读 一个位 { ucharFLAG=2; while((!wenshi)&&ucharFLAG++); //等DHT11 拉高 Delay_10us(); Delay_10us(); Delay_10us(); /// 30 us 之后 uchartemp=0; if(wenshi)uchartemp=1; //如果还是 高 得 bit = 1 ucharFLAG=2; // 否则 bit = 0 while((wenshi)&&ucharFLAG++); // 等待拉低 if(ucharFLAG==1)break;/// 超时吗?? 不太懂 ucharcomdata<<=1; // 左移一位 ucharcomdata|=uchartemp; // 把读取到的赋值 } } void DHT11(void) //温湿传感启动 { wenshi=0; // 先将 DATA数据线接口拉低 Delay_ms(19); // 延迟大于18 毫秒 复位 wenshi=1; /// 拉高 DATA 数据线 P0DIR &= ~0x40; // 将 DATA 接口 从 输出 设置为输入 Delay_10us(); Delay_10us(); Delay_10us(); Delay_10us(); /// 延时 40 us 拉低了40 us if(!wenshi) // 此时 DHT11 应该 拉低DATA数据线 { ucharFLAG=2; while((!wenshi)&&ucharFLAG++); // 等待拉高 并设置超时 ucharFLAG=2; while((wenshi)&&ucharFLAG++); // 拉高完成 接下来将会开始传输数据 COM(); // 调用读取函数 ucharRH_data_H_temp=ucharcomdata; //读取出湿度高8 COM(); ucharRH_data_L_temp=ucharcomdata; // 读取出湿度底8 COM(); ucharT_data_H_temp=ucharcomdata; // 读取出温度高8 COM(); ucharT_data_L_temp=ucharcomdata; // 读取出温度低8 COM(); ucharcheckdata_temp=ucharcomdata; // 数据校验码 wenshi=1; //// 拉高 DAta uchartemp=(ucharT_data_H_temp+ucharT_data_L_temp+ucharRH_data_H_temp+ucharRH_data_L_temp); if(uchartemp==ucharcheckdata_temp) // 校验数据 { ucharRH_data_H=ucharRH_data_H_temp; ucharRH_data_L=ucharRH_data_L_temp; ucharT_data_H=ucharT_data_H_temp; ucharT_data_L=ucharT_data_L_temp; ucharcheckdata=ucharcheckdata_temp; } wendu_shi=ucharT_data_H/10; wendu_ge=ucharT_data_H%10; shidu_shi=ucharRH_data_H/10; shidu_ge=ucharRH_data_H%10; } else //没用成功读取,返回0 { wendu_shi=0; wendu_ge=0; shidu_shi=0; shidu_ge=0; } } ZStack代码 DHT11驱动代码 分析 同裸机代码 ...

December 7, 2022 · 10 min · 晚晴

SPI详解(转载)

SPI简介 SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。 SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。 SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议 SPI特点 2.1 采用主-从模式(Master-Slave)的控制方式 SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的 Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作。 采用同步方式(Synchronous)传输数据 Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的. 数据交换(Data Exchanges) SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”. 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了. 一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access). 所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上. 在数据传输的过程中, 每次接收到的数据必须在下一次数据传输之前被采样. 如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃, 导致 SPI 物理模块最终失效. 因此, 在程序中一般都会在 SPI 传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的。 没有读和写的说法,因为实质上每次SPI是主从设备在交换数据。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。 ...

April 1, 2022 · 2 min · 晚晴