作为共用的代码文件夹,SYSTEM 文件夹下包含了 delay、 sys、 usart 等三个文件夹,有着几乎每个实验都可能用到的延时函数,位带操作,串口打印代码等。分别包含了 delay.c、 sys.c、usart.c 及其头文件 delay.h,sys.h,usart.h
delay 文件夹内包含了 delay.c 和 delay.h 两个文件,这两个文件用来实现系统的延时功能,其中包含三个3个函数:
void delay_init(u8 SYSCLK);
void delay_ms(u16 nms);
void delay_us(u32 nus);
在介绍这三个函数之前,先介绍一个 24 位的倒计数定时器 SysTick ,当计到 0 时,将从 RELOAD 寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除,就永不停息。而我们就是利用 STM32 的内部 SysTick 来实现延时的,这样既不占用中断,也不占用系统定时器
现在大多数资料提供的延时函数都可以在ucos下运行,就需要了解一下 ucoc 的时钟,ucos 运行需要一个固定的时间周期,(由OS_TICKS_PER_SEC 设置),比如 5ms(设置:OS_TICKS_PER_SEC=200 即可),在 STM32下面,一般是由 systick 来提供这个节拍,也就是 systick 要设置为 5ms 中断一次,为 ucos提供时钟节拍,而且这个时钟一般是不能被打断的(否则就不准了),需要注意的是 在 ucos 下 systick 不能再被随意更改 ,也就是说如果还想要设定其他的延时函数就需要另想办法
以 delay_us 为例,比如 delay_us(50),在刚进入 delay_us 的时候先计算好这段延时需要等待的 systick 计数次数,这里为 50 * 9(假设系统时钟为 72Mhz,那么 systick 每增加 1,就是 1/9us),然后我们就一直统计 systick 的计数变化,直到这个值变化了 50*9,一旦检测到变化达到或者超过这个值,就说明延时 50us 时间到了。
其实这些也没必要写出来的,就像之前说的,看我的这点字,还不如去看资料来的简单,但是这部分了解一下就行,实在是没有什么必要去深究(好吧,我也没看懂,太打脑壳了),可能到那种很高深的程度了会讲究这些吧,至少你现在做个什么车啊,什么什么的是用不到的
◆ delay_init 函数
延时函数初始化,用来初始化参数fac_us 和fac_ms,从这儿开始就必须培养一个概念:在之后的学习中,几乎所有需要使用的外设以及功能等等,使用前都得初始化
◆ delay_us 函数
这个就是延时nus的一个函数,具体的步骤和代码见《STM32开发指南-库函数版本》5.1.2
◆ delay_ms 函数
延时nms的一个函数,具体的步骤和代码见《STM32开发指南-库函数版本》5.1.3
sys 文件夹内包含了 sys.c 和 sys.h 两个文件。在 sys.h 里面定义了 STM32 的IO口输入读取宏定义和输出宏定义, sys.c 里面只定义了一个中断分组函数。
◆ I/O 口的位操作实现
该部分代码在 sys.h 文件中,实现对 STM32 各个I/O口的位操作,包括读入和输出。在调用这些函数之前, 必须先进行 I/O 口时钟的使能和I/O口功能定义! 而且此部分仅仅对I/O口进行输入输出读取和控制
位带操作简单的说, 就是把每个比特膨胀为一个 32 位的字,当访问这些字的时候就达到了访问比特的目的,比如说 BSRR 寄存器有 32 个位,那么可以映射到 32 个地址上, 我们去访问这 32 个地址就达到访问 32 个比特的目的。 这样我们往某个地址写 1 就达到往对应比特位写 1 的目的,同样往某个地址写 0 就达到往对应的比特位写 0 的目的。贴上位带映射图,举个例子:我们往 Address0 地址写入 1,那么就可以达到往寄存器的第 0 位 Bit0 赋值1 的目的
详细的操作看官请看指南,叨扰了
◆ 中断分组设置函数
void NVIC_Configuration()中断配置函数,这个函数在系统初始化的时候调用即可,并且永远只需要调用一次,其他关于中断的知识会在之后学习到,但是可以简单说一下中断的含义,你现在正在做一件事,此时,产生了一个中断,你转手去做另一件事,做完了,继续回来做你开始没做完的事。对…差不多是这个意思
usart文件夹包含了 usart.c 和 usart.h 两个文件。这两个文件用于串口的初始化和中断接收,这里只是针对串口 1,比如你要用串口 2 或者其他的串口,只要对代码稍作修改就可以了。 usart.c 里面包含了 2 个函数一个是 void USART1_IRQHandler(void);另外一个是void uart_init(u32 bound);里面还有一段对串口 printf 的支持代码,如果去掉,则会导致 printf无法使用,虽然软件编译不会报错,但是硬件上 STM32 是无法启动的,这段代码不要去修改。
◆ printf 函数支持
可以通过 printf 函数向串口发送我们需要的内容,方便开发过程中查看代码执行情况以及一些变量值
◆ uart_init 函数
void uart_init(u32 pclk2,u32 bound)函数是串口 1 初始化函数,在资料代码里提到了下面这个:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|
RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1, GPIOA 时钟
RCC_…代表的是时钟配置寄存器(Reset and clock control),提供给各模块时钟信号的通断。那个enable就是使能的意思,我记不清之前是写啥了,编程的时候,很多人都不知道enable什么意思
用资料的例子将 TX(PA9)设置为推挽复用输出模式,将RX(PA10)设置为浮空输入模式:
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
关于其中GPIO初始化什么什么的,之后再说吧,反正你要记住,用库函数,这些看起来很复杂的东西绝对不是让你用手去敲得,而是调用的,更改参数而已,然后优先级的部分,之后应该会结合其他东西单独写一篇,这里就不做解释了
◆ USART1_IRQHandler 函数
void USART1_IRQHandler(void)函数是串口 1 的中断响应函数,当串口 1 发生了相应的中断后,就会跳到该函数执行。中断相应函数的名字是不能随便定义的,一般我们都遵循 MDK 定义的函数名。这些函数名字在启动文件 startup_stm32f10x_hd.s 文件中可以找到。了解中断的流程就ok了,产生中断,响应中断,执行中断,结束中断,差不多就是这样
关于接受协议的部分我还有点疑惑,就不写了,之后搞懂了再加上去吧…
GPIO端口的每个位可以由软件分别配置成多种不同的输入输出模式
─ 输入浮空 ─ 开漏输出
─ 输入上拉 ─ 推挽式输出
─ 输入下拉 ─ 推挽式复用功能
─ 模拟输入 ─ 开漏复用功能
◆ 浮空输入 :由I/O引脚输入外部电平,上拉开关,下拉开关都是打开的,数据传入数据寄存器,由CPU读取外部电平
◆ 输入上拉 :即是在浮空输入的路线上,关闭上拉开关,接通VDD,连接一个大概30~50k左右的电阻,继续由CPU读取外部电平
◆ 输入下拉 :同理
◆ 模拟输入 :上拉下拉无效,模数转换的一个概念,输入通常都是0~3.3v,对应输出0或1
输出模式相较输入模式复杂一些
◆ 开漏输出(只可以输出强低电平,高电平得靠外部电阻上拉) :由CPU写入位设置/清除寄存器,输出数据寄存器,如果CPU写入1,则传到输出控制电路的电平就是1,第三步的N-MOS管截止(模电知识),最终I/O端口的输出电平由外部上拉或者下拉决定,此时也可以通过输入数据寄存器由CPU读取,但是需要注意的是,此时读取到的电平并不是CPU写入的,而是由外部决定的;当输出控制电路为0时,N-MOS管下拉到Vss,此时I/O端口输出即为0,CPU同样可以读取,且为0
◆ 开漏复用输出 :与开漏输出大致一样,区别在于输入信号是由外设的复用功能输出决定,同样的,输入为1,输出由外部上拉下拉决定,输入为0,输出为0
◆ 推挽输出(可以输出强高低电平,连接数字期器件) :控制输入的寄存器与开漏输出模式是一样的,不同在于此时P-MOS管与N-MOS管同时工作,若输入为1,则P-MOS管导通,N-MOS管截止,输出为1;若输入为0,则P-MOS管截止,N-MOS管导通,输出为0
◆ 复用推挽输出 :类似地,与推挽输出的区别在于输入通道不同,前者通过输入寄存器,而后者通过与复用功能相关的通信通道
STM32F10x系列最多有七组I/O端口,A~G,而如果我们想要使用这些端口,就需要对其对应的控制寄存器或者数据寄存器进行设置和读取对于通用的每组I/O端口会有下面的内容:
两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH)
两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR)
一个32位置位/复位寄存器(GPIOx_BSRR)
一个16位复位寄存(GPIOx_BRR)
一个32位锁定寄存器(GPIOx_LCKR)
◆ 为什么每组会有两个32位配置寄存器呢,查查资料都行,列举主要知识点就行,CRL与CRH每四个位控制一个I/O口,CRL控制0 ~ 7,CRH控制8 ~ 15,例如,GPIOA_CRL就控制PA0 ~ PA7
◆ 每个I/O口都由四位控制,低位控制输入输出以及速度,高位控制输入输出模式,如下图实例,若配置1010,则先配置低位,确定输入还是输出以及速度,然后由高位确定模式,为复用推挽输出,2mhz
◆ IDR端口输入数据寄存器,用法举例说明:GPIOA_IDR观察PA口的电平状况,若第2位读取为0/1,那么相应的PA2的输入电平就为0/1
◆ ODR端口输出数据寄存器,低16位每位控制一个I/O口输出电平为高或低,需要注意的是,在配置上拉/下拉输入的时候无法确定是上拉还是下拉的情况,这个时候就需要ODR来进行配置, 例如
GPIOA_CRL配置为1000,则表示上拉或者下拉输入,此时配置GPIOA_ODR寄存器为0的话,就表示下拉,为1则表示上拉; (ODR寄存器在输入模式下控制上拉还是下拉,在输出模式下控制输出电平)
◆ 端口位设置/清除寄存器,一个单独的选择性赋值,更改特定的某一I/O口的值,但是其他部分并不改变;
BSRR寄存器低16位,对应位设置为1,对应I/O口输出为高电平,对应位设置为0,则对应I/O口不产生任何影响,高16位相反;其实最终目的在于控制ODR寄存器,由CPU写入数据,通过BSRR来间接设置ODR,或者直接设置ODR寄存器,都可以的,但是BSRR寄存器只改变设置为1的端口,为0的端口不会改变原来的状态;而在ODR寄存器中,是针对所有对象的,设置为1的输出高电平,设置为0的输出低电平,简单来说,在实时系统中,如果要更改端口电平,通过ODR就必须得先读取数据,再重新写入修改后的全部数据,而对BSRR来说,就只需要更改修改内容就OK了
这个就很好理解了,就是每个端口不止一个功能,然后哪些端口有复用功能,查看芯片手册就OK了,常见的一些随着接触与运用,你…能背下来的
重映射的话,简单说就是…端口的更换?单片机每一个管脚的功能都是固定的,重映射功能,可以把具有特殊功能的管脚,定义到其他管脚上去,而且这个功能一般的单片机是没有的;要实现这个功能的话,可以先了解一下:
◆ 第一步启用重映射的时钟,包括io时钟,还有外设时钟
◆ 使能AFIO功能的时钟(辅助功能时钟)
AFIO_MAPR(配置复用功能重映射)
◆ 进行重映射
这个用的是比较少的,所以可以先了解一下,先熟练掌握程序了,在考虑这些吧