STM32F103学习3:通过器件参考手册和具体程序学习I/O输出操作(MDK软件仿真+硬件实验)
首先还是贴一下这个LED例程的部分代码:
1 int main() 2 { 3 4 Stm32_Clock_Init();//系统时钟设置 5 RCC->APB2ENR |= 0x00000001; //开启afio时钟 6 // AFIO->MAPR = (0x00FFFFFF & AFIO->MAPR)|0x04000000; //关闭JTAG 7 8 RCC->APB2ENR|=0X0000001c;//先使能外设IO PORTa,b,c时钟 9 10 GPIOB->CRH=0X33333333; //推挽输出 11 GPIOB->CRL=0X33333333; //推挽输出 12 GPIOC->CRH=0X33333333; //推挽输出 13 GPIOC->CRL=0X33333333; //推挽输出 14 GPIOA->CRH=0X33333333; //推挽输出 15 GPIOA->CRL=0X33333333; //推挽输出 16 17 while (1) 18 { 19 delay_ms(50); 20 21 GPIOB->ODR=0; //全部输出0 22 GPIOA->ODR=0; 23 GPIOC->ODR=0; 24 25 delay_ms(50); 26 27 GPIOB->ODR=0xffffffff; //全部输出1 28 GPIOA->ODR=0xffffffff; 29 GPIOC->ODR=0xffffffff; 30 } 31 }
时钟设置先暂时不看了,先看I/O部分的操作
1、I/O输出模式设置
GPIOB->CRH=0X33333333; //推挽输出
在器件手册中75页找到CRH寄存器列表:
这里可以看到y=8...15意思是能控制8~15腿
以一个管脚为例,0x3等于二进制的0011,
MODEy的1:0位为11,即为输出模式,最大速度为50MHz。(BTW:点一个闪烁的LED灯哪能到得了这么快的速度)
在此基础上,CNFy为00,即为输出模式的推挽输出模式。
GPIOB->CRL=0X33333333; //推挽输出
CRL寄存器的图就不贴了,不同就是这里的y=0...7,能控制0~7腿
GPIOA、GPIOB、GPIOC,所有的管脚都设置为了推挽输出的模式
管脚模式这就设置完成了,下面看一下管脚操作:
2.管脚置高、管脚置低
GPIOB->ODR=0; //全部输出0
结合这一句来看:
GPIOB->ODR=0xffffffff; //全部输出1
一会儿全1,一会儿全0,有点《三体》里整个宇宙都同时为你而闪烁的意味呢。。。
我们找到ODR寄存器:
发现这个寄存器没法进行单独位的操作,只能以16位的形式操作。
提示说独立位控制的置1置0还是去看看BSRR寄存器吧
y=0~15
给31:16位赋值1则置低该位,给15:0为赋值1则置高该位,但是哪个赋值0的话那个管脚就不会鸟你。而置高是优先的。
(这样就可以让宇宙中的某个星系为你而单独的闪烁了!)
首先修改代码:
注释掉全部涉及ODR寄存器的内容:
在原理图中找到LED管脚号为:PB8
修改程序为:
GPIOB->BSRR=0X01000000; //PB8置0
GPIOB->BSRR=0X0100; ////PB8置1
Rebuild一下
我们先用软件仿真试试:
在工具栏flash的configure flash tools中,Debug选项卡中,点击Use Simulator,点击OK:
下面点击这个小放大镜进入Debug模式:
我们发现整个界面发生了完全的变化
我们使用Analysis windows 查看一下PB8随时间变化的波形以此来判断I/O是否正确的操作了
点击:
出现了一个叫逻辑分析仪的界面
这篇帖子是keil的软件逻辑分析仪( logic analyzer)使用教程,我再次不再赘述了:
http://www.eeboard.com/bbs/thread-29726-1-1.html
结果:
成功的实现了I/O端口的翻转
但是发现还有个寄存器可以实现BSRR寄存器的高16位的置0功能:端口位清除寄存器(GPIOx_BRR)
我们退出Debug模式修改程序试试:
// GPIOB->BSRR=0X01000000; //PB8置0 GPIOB->BRR=0X0100; //PB8置0
Rebuild一下,进入Debug模式:
仿真一下,发现结果是一样的!
但是,BSRR提供给了用户一种可能,就是用一个语句,同时置高置低不同的几个端口。
3、在实际的STM32开发中,我们更多的应该是利用库函数的操作
3.5版本的库在这里下载:http://www.st.com/web/en/catalog/tools/PF257890
(现在ST公司推荐使用STM32cube工具开发,有时间我也试试。
我用的MDK4.12自带的是2.0.1的库,折腾了半天,终于设置好了库的模版。
再找找库的函数手册,找到了UM0427用户手册的PDF版本。查看一下GPIO部分的内容:
总共17个函数,先重点研究管脚初始化和输出操作的函数吧
(1)GPIO_Init()
功能:根据 GPIO_InitStruct 中指定的参数初始化外设 GPIOx 寄存器
typedef struct { u16 GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode; }GPIO_InitTypeDef;
这样一个表格加一些代码愣愣的杵在这里看着好像有点发晕,有点乱。不如我们先看例子吧,这样好理解一些。
/* Configure all the GPIOA in Input Floating mode */ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure);
这下明白了,我们初始化了一个名叫GPIO_InitStructure的结构体,结构体的原型是GPIO_InitTypeDef。
结构体GPIO_InitTypeDef里面有三个成员,一个是GPIO_Pin,管的是初始化哪个管脚。第二个是GPIO_Speed,管的是管脚的最高速度是多少。第三个是GPIO_Mode,管的是I/O管脚的模式是什么,也就是是输出还是输入,是开漏还是推挽。
(具体的GPIO_Pin、GPIO_Speed、GPIO_Mode这三者的值,在库文件手册上有表格,也不一一列举了。把UM0427手册下载下来看就OK了。)
最后,再调用一下GPIO_Init()函数,把GPIOA传进去,再把GPIO_InitStructure的地址传进去。
这样就初始化好了管脚A。
我们把LED的例程改改试一试 ;) 试试这样初始化能不能工作。但是还有个管脚时钟设置的问题,在下载到的3.5的库中找到了GPIO的例程,在初始化时添加这一句
/* GPIOB Periph clock enable */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
初始化PB8管脚为最大速度10MHz推挽输出:
/* Configure PD8 in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure);
编译,仿真看一下结果,波形没问题,使用库文件初始化成功了
(2)GPIO_SetBits()
(3)GPIO_ResetBits()
这两个函数使用上很简单
/* Set the GPIOA port pin 10 and pin 15 */ GPIO_SetBits(GPIOA, GPIO_Pin_10 | GPIO_Pin_15);
/* Clears the GPIOA port pin 10 and pin 15 */ GPIO_ResetBits(GPIOA, GPIO_Pin_10 | GPIO_Pin_15);
照着把管脚改成PB8就ok了
(4)GPIO_WriteBit()
示例如下
/* Set the GPIOA port pin 15 */ GPIO_WriteBit(GPIOA, GPIO_Pin_15, Bit_SET);
想置低就把第二个参数改为Bit_RESET
(5)GPIO_Write()
向指定数据端口写入数据
我们看一下函数定义:
/** * @brief Writes data to the specified GPIO data port. * @param GPIOx: where x can be (A..G) to select the GPIO peripheral. * @param PortVal: specifies the value to be written to the port output data register. * @retval None */ void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal) { /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); GPIOx->ODR = PortVal; }
实际上就是通过写入ODR寄存器直接实现了端口的并行I/O操作
改一下代码:
GPIO_Write(GPIOB,0x0100) ; //PB8置1
GPIO_Write(GPIOB,0x0000) ; //PB8置0
因为之前的程序灯在闪烁的定时使用的delay延时,下一篇博文学习一下定时器的配置。用定时器触发灯亮灭的转换。
I/O的输入功能因为得连接硬件,放在学习5吧。