前面已经讲解舵机PWM产生及角度控制,现在我们来讲的是舵机角度的存储。在没有存储舵机角度的时候,单片机一断电就无法保存当前的舵机角度值,所以我们需要借助EEPROM来存储舵机角度,再下次启动的时候单片机仍能恢复到上一个保存状态。
STM32的IIC-EEPROM硬件连接图
IIC简介
IIC总线是由数据线SDA和时钟线SCL构成的串行总线,可发送接收数据,IIC的特点就是一个总线上面可以接多个分机,大大的节约空间,并且他支持多个主控,其中任意能进行收发的设备都能成为主控。
EEPROM-AT24C02说明
AT24C02硬件地址
设备的地址由8bit组成,前4 bit是固定的(1010);接着的3 bit是和A2 A1 A0的硬件连接相关,通过指定这3 bit可以在统一个I2C系统里面最多连接8个AT24C02设备;第8bit用来表示读/写选择,1表示读/0表示写。AT24C02会和总线上的设备地址进行比较,假如一直则AT24C02输出一个0,不一致则返回的是standby状态。
STM32Duino开发板的A2 A1 A0地址线全接地,所以此处AT24C02的读写地址格式为:
读AT24C02:1010 0001
写AT24C02:1010 0000
AT24C02写操作
byte write:
Byte write的操作时序如上图所示。主机在发送完毕device address,并且接受到确定信息后再接着发送需要写的地址(把这个数据写到哪个地址上),然后再发送数据。当AT24C02接受到这个数据时,会输出一个0,此时主机必须发送一个停止信号。然后AT24C02进入写时序,将刚才接受到的数据写到存储单元中,并且在此期间不响应任何输入,直到写操作完成。
Pagewrite:
Page write前面几步的操作和byteqrite操作类似,只是在成功发送第一个数据之后,主机在收到AT24C02发送的确认信息后,不会发送停止信号,而是接着发送剩余的数据,对AT24C02来说就再接着发送剩余的7个字节,直到1个page的数据发送完毕之后才发送停止信号。
在页操作的时候dataword address用于表示业内的地址的低3bits会每收到一个数据就自动增长,页地址维持不变。所以,当业内地址到顶端时,此时假如还有数据,则数据将会被放到页的起始地址处,页其实地址中之前存放的数据将被覆盖。即AT24C02页操作时,写入的数据大于8 byte,则大于8 byte的数据将重新从此页起始处存放,覆盖掉之前写入的数据。
好了,EEPROM的时序读写也了解了,我们再来看看代码是怎么实现的。由于之前调试STM32105的硬件IIC一直没成功,所以果断采用软件模拟IIC读写EEPROM,毕竟条条道路通罗马。
打开STM32Duino源代码,找到i2c_fram.c这个文件并打开i2c_fram.h这个头文件
可以看到#define ADDR_24CXX 0xA0 这一句,就是定义AT24C02的硬件地址
再来看中间这一段
这是对STM32 GPIO做为输出时操作方法
这里就不说库函数操作了,因为库函数的本质是操作寄存器。当GPIO做为输出时,无非是输出0或者1。涉及到的寄存器有GPIOx_ODR、GPIO_BSRR、GPIO_BRR.下面分别介绍这三个寄存器的操作方法。
GPIOx_ODR: 端口输出数据寄存器,它的31:16位保留不用,15:0对应x的相应引脚,他只能以16位方式操作。
GPIOx_BSRR:端口位设置/复位寄存器,它的31:16位是清除位(BR15:BR0),用来复位对应的引脚;15:0位是设置位(BS15:BS0),用来设置对应的引脚。无论是BR还是BS,都是1有效,即相应位为0时无效,这样可以保证操作某个端口时对其他端口无影响。
GPIOx_BRR:端口位复位寄存器,它的31:16位保留不用,15:0相当于GPIOx_BSRR的高16位即BR。
IDR是查看引脚电平状态用的寄存器,ODR是引脚电平输出的寄存器。
事例:将GPIOB的Pin0设置位高,Pin1设置为低(要求,所有操作均对GPIOB的其他端口无影响 )
方法1: GPIOB->ODR|= GPIO_Pin_0;
GPIOB->ODR&=~GPIO_Pin_1;
方法2: GPIOB->BSRR= GPIO_Pin_0;
GPIOB->BSRR= GPIO_Pin_1<<16;
方法2: GPIOB->BSRR= GPIO_Pin_0;
GPIOB->BRR = GPIO_Pin_1;
注意:从上面可以看出采用方法二有可能将一个端口同时设置和复位,因此STM32规定,如果同时设置了BSx和BRx的对应位,BSx位起作用。
再来打开i2c_fram.c这个文件
这个就是IIC引脚初始化,前面舵机控制时已经讲过,类似就不在讲了。
下面按这个时序图讲解一下读一个地址的操作
首先,数据SDA从左边开始开始输入,START,发送一个起始信号告诉其他设备需要发送数据,DEVICE ADDRESS,器件地址寻找要发送数据的器件 前面讲了24C02的器件地址前四位是固定的1010 后三位是根据 A0,A1,A2我们硬件都接的地所以后三位都是000最后一位也就是第八位是读写命令位就是上图中R/W位,R/W上面标有READ说明是读命令也就是这一位是1 ,接下来ACK就是应答信号,判断是否还需要继续下去。有返回信号说明可以继续写入DATA了,写入完后又一个应答信号,再次判断是否还需继续写入DATA,如果没有应答信号返回,就自动发送STOP停止信号结束此次传输。
读顺序为:
检测SDA是否空闲;
->按I2C协议发出起始信号;
->发出7位器件地址和写模式(伪写);
->发出要读取的存储区首地址;
->重发起始信号;
->发出7位器件地址和写模式;
->接收数据;
->发送终止信号;
写一个地址的操作
其实和读差不多,只是R/W位换成了0也就是写命令。
写顺序:
检测SDA是否空闲;
->按I2C协议发出起始信号;
->发出7位器件地址和写模式;
->发出要读取的存储区首地址;
->用字节写入方式或页写入方式;
->发送终止信号;
起始信号,终止信号和应答信号的程序编写都是根据IIC总线操作规范写的。
好了,EEPROM的读写也讲的差不多了,现在就实际运用一下,把舵机的当前角度值保存到EEPROM中,一遍下次单片机启动的时候舵机会恢复到保存之前的角度。
打开function.c文件
这里buffer[2]舵机角度值赋值给了se_timer[]中,因此只需要把se_timer[]中的值写入EEPROM就OK
I2C_FRAM_BufferWrite()就是写入函数里面三个参数se_timer指的是se_timer[]数组的指针首地址即se_timer[0],0x00指的是数据写入AT24C02中的首地址,2指的是写入的字节数。
这样角度是写入进去了。
然后我们要再开机启动完毕会需要恢复舵机的角度怎么办呢!
打开main.c中
舵机初始化函数
I2C_FRAM_BufferRead(se_timer,0x00,2); 首先就把舵机读取出来放在se_timer[]数组中
再判断一下se_timer[]数组中是否等于0xff是为了判断这块eeprom是否存储过,如果是新的EEPROM里的数据都是0xff,若果是新的就重新给se_timer[]赋值0x3c即角度值为60°并存储到EEPROM里。
PWM_Channel3_ser1((se_timer[0]*(2000/180)+500));
PWM_Channel4_ser2((se_timer[1]*(2000/180)+500));
这里就是把角度值转换一下成PWM跳变值。
把舵机初始化函数放入数里面就可以实现舵机角度恢复啦。
到这里我们的EEPROM读写就讲完了。
|