由于ic性能有限,没有硬件的iic外设,项目上有需要使用iic从机,所以自己写了个iic从机模块,执行效率还行可以10m主频的单片机最快可以接收到100K的速度。该模块需要资源,2个外部中断配置-上升下降沿中断,来模拟iic时序,芯片编译器的问题,对结构体不是很友好,所以这边没有使用结构体对变量打包,目前只实现了简单的接受和发送,不支持eeprom那种读写方式。附带工程源码下载路径,0积分
两组io口配置,总共有4种状态:
1 SDA下降沿中断
SCL高电平 – 起始信号
SCL低电平 – 数据0 或 ACK
2 SDA上升沿中断
SCL高电平 – 停止信号
SCL低电平 – 数据1 或NACK
3 SCL 下降沿
数据交换 主机从机 写电平
4 SCL 上升沿
数据处理 主机 从机 读电平
为了更好模拟硬件,当iic空闲状态时,监测start信号,当监测到start信号时才打开SCL中断开启接受,iic接受完成或者异常时关闭,参照标准iic协议,按电平变化处理整个iic时序
实现前准备
typedef enum{
IIC_NO_OPERAT = 0,
IIC_WRITE_START,
IIC_WRITE,
IIC_WRITE_FINSH,
IIC_READ_START,
IIC_READ,
IIC_READ_FINSH,
IIC_ADDR_NO_MATCH,
IIC_OVERFLOW,
IIC_ERROR,
}IIC_STATUS_U;
#define BUFF_SIZE 15
extern u8 g_iic_status;
extern u8 g_iic_buff[BUFF_SIZE];
extern u8 g_iic_len;
void iic_slaver_init(void);
void iic_sda_proess(void);
void iic_scl_proess(void);
/``````````````````````````````````````````````c文件`````````````````````````````````````````/
typedef enum{
STEP_JETECT_ADDR = 0,
STEP_DETECT_WRITE_OR_READ,
STEP_SEND_DATA,
STEP_READ_ACK,
STEP_STOP,
STEP_RESTART,
STEP_READ_DATA,
STEP_SEND_ACK,
}IIC_STEP_U;
enum{
ACK = 0,
NACK
};
/* 本机地址 */
#define IIC_LOCAL_ADDRESS 0x78
#define iic_sda_set_out() IOSTA &= ~Port_A3
#define iic_sda_set_in() IOSTA |= Port_A3
#define iic_sda_read() PORTAbits.PA3
#define iic_sda_send_high() PORTAbits.PA3 = 1
#define iic_sda_send_low() PORTAbits.PA3 = 0
#define iic_scl_read() PORTAbits.PA4
/* scl中断开关前都需要清除标志位 */
#define scl_interrupt_on() INTFbits.INT0IF = 0;INTE |= BIT(2)
#define scl_interrupt_off() INTFbits.INT0IF = 0;INTE &= ~BIT(2)
#define sda_interrupt_flag_clr() INTFbits.INT1IF = 0
/* iic当前的状态,没有使用队列,如果主函数里面有长延时注意可能会被覆盖 */
u8 g_iic_status = IIC_NO_OPERAT;
u8 g_iic_len; /* 一次iic接收数据的长度 */
u8 g_iic_buff[BUFF_SIZE];
static u8 s_iic_temp = 0; /* 一次iic接收数据的长度 */
static u8 s_iic_num = 0; /* 处理数据对于的数组下标 */
static u8 s_iic_step = STEP_STOP;/* iic处理的程序步 */
static u8 s_iic_bit_cnt = 0;/* 一个字节处理时对于的bit号 */
实现过程 sda处理过程
/*
sda IO口中断
sda发生电平变化时,scl是高电平,则是start或者stop信号,scl是低电平,则是数据变化信号
当前不支持发送完地址之后发送指定地址数据再读,只支持发送完地址之后立即读
*/
void iic_sda_proess(void)
{
/* 数据变化信号 */
if(0 == iic_scl_read()){
return;
}
/* start 信号 */
if(0 == iic_sda_read()){
s_iic_step = STEP_JETECT_ADDR;
scl_interrupt_on();
} else if(1 == iic_sda_read()){
/*
stop 信号 逻辑分析仪抓到,应答后scl先拉高,再等sda拉高,
这个过程会触发一次scl中断,系统会开始下一次轮接收,
停止信号确认之后这里会把所有状态清除,所以可以忽略
*/
if (IIC_WRITE == g_iic_status){
g_iic_status = IIC_WRITE_FINSH;
g_iic_len = s_iic_num;
}else if (IIC_READ == g_iic_status){
g_iic_status = IIC_READ_FINSH;
}else if (IIC_ERROR == g_iic_status){
}
scl_interrupt_off();
s_iic_step = STEP_STOP;
}
s_iic_bit_cnt = 0;
s_iic_temp = 0;
s_iic_num = 0;
}
scl下降沿处理过程
/*
scl 下降沿处理过程
*/
void iic_scl_falling_edge_proess(void)
{
switch (s_iic_step){
/* 校验丛机地址,等主机动作,从机等上升沿读电平 */
case STEP_JETECT_ADDR:
break;
/* 读主机的读写位,等主机动作,从机等上升沿读电平 */
case STEP_DETECT_WRITE_OR_READ:
break;
/* 主机写数据,从机等上升沿读电平,先将io设为输入模式 */
case STEP_READ_DATA:
iic_sda_set_in();
break;
/* 主机读数据,从机发送1bit数据,先发高位,主机等上升沿读电平 */
case STEP_SEND_DATA:
iic_sda_set_out();
if (s_iic_temp&(0x80 >> s_iic_bit_cnt)){
iic_sda_send_high();
}else{
iic_sda_send_low();
}
break;
/* 从机发送应答信号,主机等上升沿读电平 */
case STEP_SEND_ACK:
/* sda的输入输出模式发生改变,清除中断标志位防止发生误触中断 */
sda_interrupt_flag_clr();
iic_sda_set_out();
iic_sda_send_low(); //应答
break;
/* 读主机ack,等主机动作,从机io设置为输入等上升沿读电平 */
case STEP_READ_ACK:
iic_sda_set_in();
break;
default:
break;
}
}
iic上升沿处理过程
/*
scl 上升沿处理过程
主机发送完一帧数据应答后,准备下一帧数据时候,scl的PB5电平会翻转两次,但接受好笑没有出错,暂时不清楚为什么会这样
*/
void iic_scl_rising_edge_proess(void)
{
switch (s_iic_step){
/* 校验丛机地址,读一个bit */
case STEP_JETECT_ADDR:
s_iic_temp <<= 1;
s_iic_temp += iic_sda_read();
if (7 <= (++s_iic_bit_cnt)){
if (IIC_LOCAL_ADDRESS == s_iic_temp){
s_iic_step = STEP_DETECT_WRITE_OR_READ;
}else{
/* 地址不匹配,关闭scl中断,重新监测起始信号 */
s_iic_step = STEP_STOP;
g_iic_status = IIC_ADDR_NO_MATCH;
scl_interrupt_off();
}
s_iic_bit_cnt = 0;
s_iic_temp = 0;
}
break;
/* 校验丛机地址成功,读主机的读写位 */
case STEP_DETECT_WRITE_OR_READ:
/*
主机要读数据,从机发数据,将数据传入temp,所以要读的数据还提前需要填入buff,
如果需要支持读不通的地址的数据的话还需要升级版本,定制协议
*/
s_iic_step = STEP_SEND_ACK;
if (1 == iic_sda_read()){
g_iic_status = IIC_READ_START;
}else if (0 == iic_sda_read()){ //主机要发数据,从机读数据
g_iic_status = IIC_WRITE_START;
s_iic_num = 0;
}
break;
/* 接收数据,读一个bit,先接高位 */
case STEP_READ_DATA:
s_iic_temp <<= 1;
if (1 == iic_sda_read()){
s_iic_temp |= 0x01;
}
if (8 <= (++s_iic_bit_cnt)){
s_iic_bit_cnt = 0;
s_iic_step = STEP_SEND_ACK;
}
break;
/* 发送完一个bit,判断1个byte是否发送完,发送完成下一步读主机ack */
case STEP_SEND_DATA:
if (8 <= ++s_iic_bit_cnt){
s_iic_bit_cnt = 0;
s_iic_step = STEP_READ_ACK;
}
break;
/* 发送完ack,准备下一byte接收 */
case STEP_SEND_ACK:
if (IIC_WRITE == g_iic_status){
/* 将接收到的数据传入buff,然后number++ */
g_iic_buff[s_iic_num++] = s_iic_temp;
s_iic_temp = 0;
s_iic_bit_cnt = 0;
/* 溢出,不能再接受数据 */
if (s_iic_num >= BUFF_SIZE){
scl_interrupt_off();
iic_sda_set_in();
s_iic_num = 0;
s_iic_step = STEP_STOP;
g_iic_status = IIC_ERROR;
}else{
s_iic_step = STEP_READ_DATA;
}
}else if (IIC_WRITE_START == g_iic_status){
g_iic_status = IIC_WRITE;
s_iic_step = STEP_READ_DATA;
s_iic_bit_cnt = 0;
}else if (IIC_READ_START == g_iic_status){
s_iic_temp = g_iic_buff[0];/* 不支持从指定地址读出 */
g_iic_status = IIC_READ;
s_iic_step = STEP_SEND_DATA;
} else {
iic_sda_set_in();
}
/* sda的输入输出模式发生改变,清除中断标志位防止发生误触中断 */
sda_interrupt_flag_clr();
break;
/* 读主机ack */
case STEP_READ_ACK:
if (1 == iic_sda_read()){ /* 主机不应答,发生错误或一次iic传输结束了 */
scl_interrupt_off();
s_iic_num = 0;
}else if (0 == iic_sda_read()){ //主机应答
s_iic_num ++;
if (s_iic_num >= g_iic_len){ //溢出
scl_interrupt_off();
s_iic_num = 0;
}else {
/* 准备下一帧数据发送 */
s_iic_temp = g_iic_buff[s_iic_num];
s_iic_step = STEP_SEND_DATA;
}
}
/* sda的输入输出模式发生改变,清除中断标志位防止发生误触中断 */
sda_interrupt_flag_clr();
break;
default:
break;
}
}