一、了解I2C
首先我们要了解I2C的基本原理
当IIC处于空闲状态的时候,SDA和SCL都处于高电平状态,
当IIC通信开始信号,SCL保持高电平,SDA从高电平变成低电平(SCL=1,SDA=1->0),
当IIC通信结束信号,SCL保持高电平,SDA从低电平变成高电平(SCL=1,SDA=0->1)。
IIC通信开始后,发送8位数据信号,SCL拉低,
SDA发送数据最高位(7:1则高电平,0则低电平),然后SDA保持不变直到SCL下次低电平;
SDA发送数据(改变电平)后,SCL拉高,接收端读取信号后SCL再拉低。
共8次发送8个数据位【7:0】
在发送8个数据后,输出端的SDA将释放总线,交给接收端来控制;
SCL拉低,输出端SDA释放总线,接收端如果应答:SDA拉低 不应答:SDA拉高
二、AS5600的读取
AS5600是12位的霍尔磁编码器,它的地址是0x36,只需要读取0x0C、0x0D这两个寄存器就可以读出角度的原始数据,再将其乘以360,再除以4096,就可以获得角度值。
1.配置引脚
我们使用io模拟IIC通信,使用的是PB6,PB7;
这里我们直接使用正点原子的F103 IIC实验代码;
首先将sys.h和sys.c复制到我们的程序中(我使用的是模板,带oled驱动,方便调试)
sys两个文件程序我放在文章后面,方便没有下载正点原子例程的读者;
将myiic.h复制过来,再加上三个函数,再定义一下0x0c和0x0d两个寄存器地址;
#ifndef _AS5600_H
#define _AS5600_H
#include "sys.h"
//IO方向设置
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} //SDA切换为输入模式
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //SDA切换为输出模式
//IO操作函数
#define IIC_SCL PBout(6) //SCL输出
#define IIC_SDA PBout(7) //SDA输出
#define READ_SDA PBin(7) //SDA输入
#define _raw_ang_hi 0x0c
#define _raw_ang_lo 0x0d
//IICËùÓвÙ×÷º¯Êý
void IIC_Init(void); //³õʼ»¯IICµÄIO¿Ú
void IIC_Start(void); //·¢ËÍIIC¿ªÊ¼ÐźÅ
void IIC_Stop(void); //·¢ËÍIICÍ£Ö¹ÐźÅ
void IIC_Send_Byte(u8 txd); //IIC·¢ËÍÒ»¸ö×Ö½Ú
u8 IIC_Read_Byte(unsigned char ack);//IIC¶Áȡһ¸ö×Ö½Ú
u8 IIC_Wait_Ack(void); //IICµÈ´ýACKÐźÅ
void IIC_Ack(void); //IIC·¢ËÍACKÐźÅ
void IIC_NAck(void); //IIC²»·¢ËÍACKÐźÅ
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
u8 AS5600_ReadOneByte(u16 ReadAddr);
void AS5600_WriteOneByte(u16 WriteAddr,u8 WriteData);
u16 AS5600_ReadTwoByte(u16 ReadAddr_hi,u16 ReadAddr_lo);
#endif
#include "as5600.h"
#include "Delay.h"
//³õʼ»¯IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //ʹÄÜGPIOBʱÖÓ
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD ; //注意IIC SDA线需要开漏输出,不然读取不到数据
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 Êä³ö¸ß
}
//²úÉúIICÆðʼÐźÅ
void IIC_Start(void)
{
SDA_OUT(); //sdaÏßÊä³ö
IIC_SDA=1;
IIC_SCL=1;
Delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
Delay_us(4);
IIC_SCL=0;//ǯסI2C×ÜÏߣ¬×¼±¸·¢ËÍ»ò½ÓÊÕÊý¾Ý
}
//²úÉúIICÍ£Ö¹ÐźÅ
void IIC_Stop(void)
{
SDA_OUT();//sdaÏßÊä³ö
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
Delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//·¢ËÍI2C×ÜÏß½áÊøÐźÅ
Delay_us(4);
}
//µÈ´ýÓ¦´ðÐźŵ½À´
//·µ»ØÖµ£º1£¬½ÓÊÕÓ¦´ðʧ°Ü
// 0£¬½ÓÊÕÓ¦´ð³É¹¦
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDAÉèÖÃΪÊäÈë
IIC_SDA=1;Delay_us(1);
IIC_SCL=1;Delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//ʱÖÓÊä³ö0
return 0;
}
//²úÉúACKÓ¦´ð
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
Delay_us(2);
IIC_SCL=1;
Delay_us(2);
IIC_SCL=0;
}
//²»²úÉúACKÓ¦´ð
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
Delay_us(2);
IIC_SCL=1;
Delay_us(2);
IIC_SCL=0;
}
//IIC·¢ËÍÒ»¸ö×Ö½Ú
//·µ»Ø´Ó»úÓÐÎÞÓ¦´ð
//1£¬ÓÐÓ¦´ð
//0£¬ÎÞÓ¦´ð
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//ÀµÍʱÖÓ¿ªÊ¼Êý¾Ý´«Êä
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
Delay_us(2); //¶ÔTEA5767ÕâÈý¸öÑÓʱ¶¼ÊDZØÐëµÄ
IIC_SCL=1;
Delay_us(2);
IIC_SCL=0;
Delay_us(2);
}
}
//¶Á1¸ö×Ö½Ú£¬ack=1ʱ£¬·¢ËÍACK£¬ack=0£¬·¢ËÍnACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDAÉèÖÃΪÊäÈë
for(i=0;i<8;i++ )
{
IIC_SCL=0;
Delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
Delay_us(1);
}
if (!ack)
IIC_NAck();//·¢ËÍnACK
else
IIC_Ack(); //·¢ËÍACK
return receive;
}
//ÔÚAS5600Ö¸¶¨µØÖ·¶Á³öÒ»¸öÊý¾Ý
u8 AS5600_ReadOneByte(u16 ReadAddr)
{
u8 temp=-1;
IIC_Start();
IIC_Send_Byte((0X36<<1)|0x00); //·¢ËÍдÃüÁî
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr); //·¢Ë͵ØÖ·
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte((0X36<<1)|0x01); //½øÈë½ÓÊÕģʽ
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop();//²úÉúÒ»¸öÍ£Ö¹Ìõ¼þ
return temp;
}
//ÔÚAS5600Ö¸¶¨µØÖ··¢ËͳöÒ»¸öÊý¾Ý
void AS5600_WriteOneByte(u16 WriteAddr,u8 WriteData)
{
IIC_Start();
IIC_Send_Byte((0X36<<1)|0x00); //·¢ËÍдÃüÁî
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr); //·¢Ë͵ØÖ·
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(WriteData); //·¢ËÍÊý¾Ý
IIC_Wait_Ack();
IIC_Stop();//²úÉúÒ»¸öÍ£Ö¹Ìõ¼þ
Delay_ms(10);
}
//¶ÁÈ¡Á½Î»Êý¾Ý
u16 AS5600_ReadTwoByte(u16 ReadAddr_hi,u16 ReadAddr_lo)
{
u16 TwoByte_Data=-1;
u8 hi_Data=0,lo_Data=0;
hi_Data=AS5600_ReadOneByte(ReadAddr_hi);
lo_Data=AS5600_ReadOneByte(ReadAddr_lo);
TwoByte_Data = (hi_Data<<8)|lo_Data;
return TwoByte_Data;
}
as5600.c
这里我们主要讲AS5600_ReadOneByte()这个函数
u8 AS5600_ReadOneByte(u16 ReadAddr)
{
u8 temp=-1;
IIC_Start();
IIC_Send_Byte((0X36<<1)|0x00); //
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr); //
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte((0X36<<1)|0x01); //
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop();//
return temp;
}
1.先开始IIC信号
IIC_Start();
2. 发送5600的写入地址
0x36是他的地址,但是要左移一位,因为最后一位是读写位,1是读,0是写
00110110<<1 |0 = 01101100 = 0x6C = 0x36<<1|0
IIC_Send_Byte((0X36<<1)|0x00); //发送写命令
IIC_Wait_Ack(); //等待应答
3.发送读取的寄存器地址 0x0C或者0x0D
IIC_Send_Byte(ReadAddr); //发送地址
IIC_Wait_Ack(); //等待应答
4.开始信号
IIC_Start();
5.发送5600的读取地址
0x36是他的地址,但是要左移一位,因为最后一位是读写位,1是读,0是写
00110110<<1 |1 = 01101101 = 0x6D=0x36<<1|1
IIC_Send_Byte((0X36<<1)|0x01); //进入接收模式
IIC_Wait_Ack();
6.读取数据
temp=IIC_Read_Byte(0); //读取数据
IIC_Stop(); // 停止信号
return temp; //返回读取数据
讲解完IIC的读取数据之后,我们来使用 AS5600_ReadTwoByte()函数读取两个寄存器的值
u16 AS5600_ReadTwoByte(u16 ReadAddr_hi,u16 ReadAddr_lo)
{
u16 TwoByte_Data=-1;
u8 hi_Data=0,lo_Data=0;
hi_Data=AS5600_ReadOneByte(ReadAddr_hi);
lo_Data=AS5600_ReadOneByte(ReadAddr_lo);
TwoByte_Data = (hi_Data<<8)|lo_Data;
return TwoByte_Data;
}
此函数是使用读取两个寄存器的值,对位的操作将两个8位的寄存器数据合并为16位数据
因为as5600是12位精度的霍尔传感器,一个8位寄存器存不下,
所以需要两个8位的寄存器来存储他的值
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "sys.h"
#include "as5600.h"
int main(void)
{
IIC_Init();
OLED_Init();
OLED_ShowChar(1, 1, 'A');
u16 raw_num = 0;
u16 JIAODUnum = 0;
while (1)
{
raw_num = AS5600_ReadTwoByte(_raw_ang_hi,_raw_ang_lo); //读取两个寄存器的值
OLED_ShowNum(2,1,raw_num,5); //在屏幕显示
JIAODUnum = (raw_num*360)/4096; //对寄存器值进行处理得到角度值
OLED_ShowNum(3,1,JIAODUnum,3);//在屏幕显示
Delay_ms(100);
}
}
使用这个函数之后,我们就可以读取到传感器原始的角度数据
因为是12位精度的传感器,所以我们得到的数据也是12位的,即0~4095;
因为0度即360度所以我们将数据除4096再乘以360,即可得到0-359度的数据
使用合适的调试工具比如屏幕和串口,我们即可得到角度数据。
附:sys.c
#include "sys.h"
void WFI_SET(void)
{
__ASM volatile("wfi");
}
//关闭所有中断
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
}
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
sys.h
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"
//0,不支持ucos
//1,支持ucos
#define SYSTEM_SUPPORT_OS 0 //定义系统文件夹是否支持UCOS
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
//以下为汇编函数
void WFI_SET(void); //执行WFI指令
void INTX_DISABLE(void);//关闭所有中断
void INTX_ENABLE(void); //开启所有中断
void MSR_MSP(u32 addr); //设置堆栈地址
#endif