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 · 晚晴