在之前的文章中分析过在52上的IIC时序,也测试过stm32的自带IIC功能,这里大致写下如何模拟stm32上的IIC。实验硬件基于stm32f103c8t6
废话不多说,先直接上代码。
一、源码
头文件
#include "stdint.h"
#include "gpio.h"
#ifndef __IIC_H__
#define __IIC_H__
/*********************引脚和主频根据情况更换*****************************/
#define CPU_FREQUENCY_MHZ 72 // STM32时钟主频
#define IIC_GPIO_PORT GPIOB // GPIO端口
#define IIC_SCL_PIN GPIO_PIN_9 // 连接到SCL时钟线的GPIO引脚
#define IIC_SDA_PIN GPIO_PIN_8 // 连接到SDA数据线的GPIO引脚
#define IIC_SCL_1() IIC_GPIO_PORT->BSRR = IIC_SCL_PIN // SCL = 1
#define IIC_SCL_0() IIC_GPIO_PORT->BSRR = (uint32_t)IIC_SCL_PIN << 16U // SCL = 0
#define IIC_SDA_1() IIC_GPIO_PORT->BSRR = IIC_SDA_PIN // SDA = 1
#define IIC_SDA_0() IIC_GPIO_PORT->BSRR = (uint32_t)IIC_SDA_PIN << 16U // SDA = 0
#define IIC_SDA_READ() (IIC_GPIO_PORT->IDR & IIC_SDA_PIN) // 读SDA引脚
#define IIC_SCL_READ() (IIC_GPIO_PORT->IDR & IIC_SCL_PIN) // 读SCL引脚
void iic_start(void); //开始信号
void iic_stop(void); //停止信号
void iic_send_byte(uint8_t _ucByte); //发送一个字节
void iic_ack(void); //应答信号
uint8_t iic_read_byte(void); //读取一个字节
uint8_t iic_wait_ack(void); //等待应答信号
#endif
.m文件
#include "iic.h"
void delay_us(__IO uint32_t delay) {
int last, curr, val;
int temp;
while (delay != 0) {
temp = delay > 900 ? 900 : delay;
last = SysTick->VAL;
curr = last - CPU_FREQUENCY_MHZ * temp;
if (curr >= 0){
do{
val = SysTick->VAL;
}
while ((val < last) && (val >= curr));
} else{
curr += CPU_FREQUENCY_MHZ * 1000;
do{
val = SysTick->VAL;
}
while ((val <= last) || (val > curr));
}
delay -= temp;
}
}
// IIC 至少0.6us,1us即可
void iic_delay_1us(void) {
delay_us(1);
}
// SCL保持高电平期间,SDA由高电平变成低电平,产生一个下降沿。
void iic_start(void) {
IIC_SDA_1();
IIC_SCL_1();
iic_delay_1us();
IIC_SDA_0();
iic_delay_1us();
}
// SCL保持高电平期间,SDA由低电平变成高电平,产生一个上升沿。
void iic_stop(void){
IIC_SDA_0();
IIC_SCL_1();
iic_delay_1us();
IIC_SDA_1();
iic_delay_1us();
}
void iic_send_byte(uint8_t _ucByte){
uint8_t i;
for (i = 0; i < 8; i++) {
IIC_SCL_0();
if (_ucByte & 0x80){
IIC_SDA_1();
} else{
IIC_SDA_0();
}
iic_delay_1us();
IIC_SCL_1();
iic_delay_1us();
IIC_SCL_0();
// 发送最后一位
if (i == 7) {
IIC_SDA_1(); // 释放总线
}
_ucByte <<= 1; // 左移一个bit
iic_delay_1us();
}
}
uint8_t iic_read_byte(void){
uint8_t i;
uint8_t value = 0;
for (i = 0; i < 8; i++) {
value <<= 1;
IIC_SCL_1();
iic_delay_1us();
if (IIC_SDA_READ()) {
value++;
}
IIC_SCL_0();
iic_delay_1us();
}
return value;
}
uint8_t iic_wait_ack(void){
uint8_t result;
IIC_SCL_1();
iic_delay_1us();
result = IIC_SDA_READ() ? 1 :0;
IIC_SCL_0();
iic_delay_1us();
return result;
}
void iic_ack(void){
IIC_SDA_0();
iic_delay_1us();
IIC_SCL_1();
iic_delay_1us();
IIC_SCL_0();
iic_delay_1us();
IIC_SDA_1();
}
uint8_t iic_CheckDevice(uint8_t _Address){
uint8_t ucAck;
if (IIC_SDA_READ() && IIC_SCL_READ()){
iic_start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
iic_send_byte(_Address | 0);
ucAck = iic_wait_ack();
iic_stop();
return ucAck;
}
return 1;
}
void Device_WriteData(uint8_t DeciveAddr,uint8_t *Data,int size){
int count=size;
uint8_t *pData=Data;
//起始信号
iic_start();
//发送器件地址
iic_send_byte(DeciveAddr);
//等待应答
iic_wait_ack();
while(count--)
{
//发送数据
iic_send_byte(*pData++);
//等待应答
iic_wait_ack();
}
//结束信号
iic_stop();
}
二、CubeMX配置
实验用了PB8和PB9,设置的都是开漏输出式,因为在这个模式下有如下说明:
在配置成开漏输出时,可以读IDR寄存器。
三、源码分析
整个源码的格式写法和之前手撸51中的IIC的类似,包括start,stop, ack等的时序。
这里是直接操作的寄存器来输出/输入数据。输出时比如SDA,设置BSRR,读取状态时,比如SDA中的ack,直接读取IDR寄存器。
#define IIC_SCL_1() IIC_GPIO_PORT->BSRR = IIC_SCL_PIN // SCL = 1
#define IIC_SCL_0() IIC_GPIO_PORT->BSRR = (uint32_t)IIC_SCL_PIN << 16U // SCL = 0
#define IIC_SDA_1() IIC_GPIO_PORT->BSRR = IIC_SDA_PIN // SDA = 1
#define IIC_SDA_0() IIC_GPIO_PORT->BSRR = (uint32_t)IIC_SDA_PIN << 16U // SDA = 0
#define IIC_SDA_READ() (IIC_GPIO_PORT->IDR & IIC_SDA_PIN) // 读SDA引脚
#define IIC_SCL_READ() (IIC_GPIO_PORT->IDR & IIC_SCL_PIN) // 读SCL引脚
其它地方就没什么分析的了,和51的代码都一致,O(∩_∩)O哈哈~
完整代码