1 前言
这个帖子是个人学习STM32IIC通信的一个小总结,实现了STM32F103C8读写DS1307。
多年前买了原子哥的战舰开发板学习STM32,后来种种原因停滞了几年。前不久重新学习STM32,在空暇之余按照原子哥的不完全手册依次学习芯片各个自带资源,不过进度倒是比较慢了。
学习IIC通信花了不少时间,这期间吃了亏,并再一次意识到看芯片手册的重要性。这里把操作DS1307的一些要点按自己的理解总结出来,希望需要的人少走弯路。
2 写DS1307
图1截图自DS1307芯片手册。展示了主机写、DS1307接收模式时的数据流。一般是主机修改ds1307内储存的时间实现校时。步骤为:
1. 主机发送起始(start)信号
2. 主机发送DS1307器件地址(0XD0);
3. 主机等待DS1307响应ack
4. 主机发送要写入的DS1307内部寄存器的起始地址(秒的地址为0x00,星期的地址是0x04)
5. 主机等待DS1307响应ack
6. 主机依次发送DS1307内部寄存器的新数据
7. 主机等待DS1307响应ack
8. 主机发送停止(stop)信号
上述过程中,
- l如果主机只发送1个DS1307内部寄存器的新数据,之后就发送停止信号,那么就只修改了起始地址的数据。比如这个起始地址是0x01(分钟地址),新数据是0x32,那么分钟寄存器的值就更新为0x32。
- l如果主机发送n个DS1307内部寄存器的新数据,那么就修改起始地址以及起始地址之后n-1个寄存器的数据,比如这个起始地址是0x02,新数据是{0x08,0x22,0x03},寄存器0x02、0x03、0x04的值都会被修改,即小时、日、星期寄存器的新值就分别是0x08、0x22、0x03
写一字节函数:
void Ds1307writeByte(u8 addr,u8 dat)
{
u8 ack, i=0;
IIC_soft_start();
IIC_soft_write_byte(0xd0);
IIC_wait_ack();
IIC_soft_write_byte(addr);
IIC_wait_ack();
IIC_soft_write_byte(dat);
IIC_wait_ack();
IIC_soft_stop();
}
初始化DS1307函数,实际是就是多字节写入DS1307的函数
void InitDS1307(void)
{
u8 i,ack;
u8 InitTime[7] ={0x00,0x32,0x08,0x04,0x01,0x10,0x20};//2020-10-1 THU 08:32:00
IIC_soft_start();
IIC_soft_write_byte(0xd0);
IIC_wait_ack();
IIC_soft_write_byte(0x00);
IIC_wait_ack();
for(i=0;i<7;i++)
{
Ds1307writeByte(i,InitTime);
IIC_wait_ack();
}
IIC_soft_stop();
}
3 读DS1307
图2展示了主机读、DS1307发送模式时的数据流。一般是主机读取ds1307内储存的时间数据。步骤为:
1. 主机发送起始(start)信号
2. 主机发送DS1307器件地址(0XD0);
3. 主机等待DS1307响应ack
4. 主机发送要读取的DS1307内部寄存器的起始地址
5. 主机等待DS1307响应ack
6. 主机再次发送起始(start)信号(图中红圈部分)
7. 主机发送读命令(0XD1);
8. 主机等待DS1307响应ack
9. 主机依次接收n字节DS1307内部寄存器的数据(每接收1字节数据,就发送响应ack一次),读最后一个字节后发送响应nack一次表示通知DS1307不要再发送数据了;
10. 主机发送停止(stop)信号
上述过程中,
l主机接收的字节数实际由主机自己决定,接收1个字节就发送停止信号,那么久相当于只读取了一个字节数据;接收6个字节就发送停止信号,那么就相当于读取了6个字节数据。
l假设发送的起始地址是0x01,接收了5个字节就发送停止信号,就相当于读取了寄存器0x01、0x02、0x03、0x04、0x05这5个寄存器的值
读一字节函数:
u8 DS1307_read_one_byte(u8 addr)
{
u8 dat,ack;
IIC_soft_start();
IIC_soft_write_byte(0xd0);
ack=IIC_wait_ack();
IIC_soft_write_byte(addr);
ack=IIC_wait_ack();
IIC_soft_start();
IIC_soft_write_byte(0xd1);
ack=IIC_wait_ack();
dat=IIC_soft_read_byte(1); //参数1—发送nack
IIC_soft_stop();
return dat;
}
读取多个字节
void DS1307_read_n_bytes(u8 addr_start,u8 byte_count,u8 *buf)
{
u8 ack,i;
IIC_soft_start();
IIC_soft_write_byte(0xd0);
ack=IIC_wait_ack();
IIC_soft_write_byte(addr_start);
ack=IIC_wait_ack();
IIC_soft_start();
IIC_soft_write_byte(0xd1);
ack=IIC_wait_ack();
for(i=0;i<byte_count-1;i++)
{
*buf++=IIC_soft_read_byte(0);//参数0—发送ack
}
*buf =IIC_soft_read_byte(1); //参数1—发送nack
IIC_soft_stop();
}
4 总结
实现IIC通信是最重要的一步,不过这里只说IIC应用,具体通信方式参考原子哥的不完全手册就行了。说句题外话,不完全手册是以读写AT24C02为例子进行说明的,要是读写AT24C32及更大容量的系列芯片,需要将地址分高低字节分别写入才能正确通信。
还有一点就是关于STM32引脚配置的问题,我本次使用PB4/PB5作为驱动IO,由于复位后PB4是作为JTAG的一个引脚,因此需要将PB4配置为普通IO口,参看复用重映射和调试I/O配置寄存器(AFIO_MAPR)的SWJ_CFG位,配置值的作用如图3所示。
我使用的是一个最小系统,需要使用SW的下载功能,因此只关闭JTAG,配置代码如下:
RCC->APB2ENR |=1<<0;//开启引脚复用时钟
AFIO->MAPR |=2<<24; //010:禁止JTAG-DP,启用SW-DP;
最后效果如图4所示,时钟模块是“YX65412-DS1307时钟模块”,带有AT24C32储存,读写DS1307的时候必须5v供电。读写AT24C32的时候3.3v供电即可。