最近打算读一个传感器,时序要求比较严。
批量测试同时读取20个,16ms一组数据,使用一个STM32读取会有影响,就想了一种方法,使用8位机来读取,然后数据转发给STM32。
芯片间采用模拟IIC通讯,并且通讯之前加了一个SDA数据线中断通知(本次是每个8位机单独连接各自的数据线,未使用总线模式,可根据需求改进)。
8位机速度比较慢,处理的比较简单,所以作为从机阻塞等待时钟或数据超时。
整体思路,8位机读取数据,成功后产生一个SDA低电平,等待STM返回SCL低电平后,立即开始等待START信号(8位机反应比较慢,要么增加STM里面的延时,速度会慢;要么等待代码写在同一函数,不要跳转)。
从机读取完一个字节(自定义为读取指令0x44,配置指令0x88,根据需求自己改),返回一个字节0x44或0x88,接着数据校验和,数据高8位,数据低8位。
调试模拟IIC必须的使用示波器,抓到详细的波形,去判断每一步该怎么延时,怎么控制。尤其是起始信号,ASK信号。
8位机使用IO型 FT60F211,引脚定义如下:
增加了模拟串口发送,S1和P1为数据读取引脚。
* FT60F211 SOP8
* // Memory: Flash 1KX14b, EEPROM 128X8b, SRAM 64X8b
* ----------------
* VDD-----------|5(VDD) (GND)14|-----------GND
* NC------------|7(PA5) (PA0)12|------------S1
* IICSlaver_SCL-|8(PA4) (PA1)11|------------P1
* IICSlaver_SDA-|9(PA3) (PA2)10|-------UARTTXD
引脚定义,SDA,SCL在8位机配置为 上拉
#define IICSlaver_SCL PA4
#define IICSlaver_SDA PA3
#define SCL_OUT TRISA4 =0
#define SDA_OUT TRISA3 =0
#define SDA_IN TRISA3 =1
IIC的时序不再多说,主要有下面几种状态:
等待SCL高低电平
等待SDA高低电平
发送高低电平
组成起始信号,停止信号,ASK信号。
//为所使用的硬件平台的寄存器配置 253=1.5ms
#define WAIT_IIC_SCL_HIGH IICSlaverWaitTimeout = 0;while ( !IICSlaver_SCL )\
{IICSlaverWaitTimeout++;if(IICSlaverWaitTimeout>253){IICSlaverWaitTimeout = 0;break;}}
#define WAIT_IIC_SCL_LOW IICSlaverWaitTimeout = 0;while ( IICSlaver_SCL )\
{IICSlaverWaitTimeout++;if(IICSlaverWaitTimeout>253){IICSlaverWaitTimeout = 0;break;}}
#define WAIT_IIC_SDA_HIGH IICSlaverWaitTimeout = 0;while ( !IICSlaver_SDA )\
{IICSlaverWaitTimeout++;if(IICSlaverWaitTimeout>253){IICSlaverWaitTimeout = 0;break;}}
#define WAIT_IIC_SDA_LOW IICSlaverWaitTimeout = 0;while ( IICSlaver_SDA )\
{IICSlaverWaitTimeout++;if(IICSlaverWaitTimeout>253){IICSlaverWaitTimeout = 0;break;}}
#define IIC_WAIT_START WAIT_IIC_SCL_HIGH;WAIT_IIC_SDA_LOW
#define IIC_WAIT_STOP WAIT_IIC_SCL_LOW;WAIT_IIC_SCL_HIGH;WAIT_IIC_SDA_HIGH
#define IIC_SLAVE_SEND_LOW WAIT_IIC_SCL_LOW; IICSlaver_SDA = 0; SDA_OUT; \
WAIT_IIC_SCL_HIGH; WAIT_IIC_SCL_LOW;SDA_IN
#define IIC_SLAVE_SEND_HIGH WAIT_IIC_SCL_LOW; IICSlaver_SDA = 1; SDA_OUT; \
WAIT_IIC_SCL_HIGH; WAIT_IIC_SCL_LOW;SDA_IN
#define IIC_SLAVE_SEND_ACK IIC_SLAVE_SEND_LOW
#define IIC_SLAVE_SEND_NAK IIC_SLAVE_SEND_HIGH
/*-------------------------------------------------
* 函数名:IICSlaver_Send_Byte
* 功能: IICSlaver发送一个字节
* 输入: 写入要发送的一个人字节数据txd
* 输出: 无
--------------------------------------------------*/
void IICSlaver_Send_Byte(uint8_t txd)
{
uint8_t i;
for(i = 0; i < 8; i ++)
{
WAIT_IIC_SCL_LOW;
if ( txd & 0x80 )
IICSlaver_SDA = 1;
else
IICSlaver_SDA = 0;
SDA_OUT;
txd <<= 1;
WAIT_IIC_SCL_HIGH;
}
WAIT_IIC_SCL_LOW;
SDA_IN;
WAIT_IIC_SCL_HIGH;
}
/*-------------------------------------------------
* 函数名:IICSlaver_Read_Byte
* 功能: IICSlaver读一个字节
* 输入: 无
* 输出: 读出存储器里面的数据并返回receive
--------------------------------------------------*/
uint8_t IICSlaver_Read_Byte(void)
{
uint8_t i,receive=0;
for(i = 0; i < 8; i ++)
{
WAIT_IIC_SCL_LOW;
WAIT_IIC_SCL_HIGH;
receive <<= 1; //先移位,再读数
if(IICSlaver_SDA)
receive |= 0x01;
else
receive |= 0x00;
}
return receive;
}
/*-------------------------------------------------
* 函数名:IICSlaver_WRITE
* 功能: 发送中断,IICSlaver数据data,sum
* 输入: sum,data
* 输出: 无
--------------------------------------------------*/
uint8_t IICSlaver_WRITE( uint16_t pirdata)
{
uint32_t IICSlaverIntTimeout = 0;
uint8_t datA,datB,sum,reg = 0;
datA=pirdata>>8;
datB=pirdata;
sum = datA + datB;
IICSlaver_SDA=0; //INT
SDA_OUT; //SDA线输出
while(IICSlaver_SCL == 1)
{
IICSlaverIntTimeout ++;
if(IICSlaverIntTimeout > 800)//800=5ms 1000=6.2ms 1500=8.5ms
{
IICSlaverIntTimeout = 0;
SDA_IN;
// IICSlaver_SDA=1; //发送I2C总线结束信号
return 0;//time out
}
}
SDA_IN;
// IICSlaver_SDA=1; //发送I2C总线结束信号
IIC_WAIT_START;
reg = IICSlaver_Read_Byte();
IIC_SLAVE_SEND_ACK;
IICSlaver_Send_Byte(0x44);//0x88config 0x44data
IICSlaver_Send_Byte(sum);
IICSlaver_Send_Byte(datA);
IICSlaver_Send_Byte(datB);
IIC_WAIT_STOP;
return reg;
}
主函数里面带有一些重新配置跳转的判断
/*-------------------------------------------------
* 函数名: main
* 功能: 主函数
* 输入: 无
* 输出: 无
--------------------------------------------------*/
void main()
{
uint8_t modeerr,datA,datB,SUM;
POWER_INITIAL(); //系统初始化
TIMER0_INITIAL();
DelayMs(200);
Reconfig:
while(IICSlaver_ReadMode() == 0)
{
DelayMs(10);
}
while(1)
{
if(GetData)
{
GetData = 0;
//Send SDA INT , DATA
modeerr = IICSlaver_WRITE(DataValue&0xffff);
if((modeerr&0xCC)==0x88)//0x88config 0x44data
goto Reconfig;
//UART SEND 9600 7.5ms
//UART SEND 19200 4ms
datA=PIRDataValue>>8;
datB=PIRDataValue;
SUM=0x47+datA+datB;
WByte(0xAA);
WByte(0xAA);
WByte(0xF1);
WByte(2);//len
WByte(datA);
WByte(datB);
// SUM=0xAA+0xAA+0xF1+4+datA+datB+datC+datD;
WByte(SUM);
}
}
}
从机完成。
主机开始,模拟IIC用的就比较多了,基础部分信号产生,应答,发送字节,都是通用的(考虑延时),只需要修改读取函数。
下面是与上面配套的读取流程:
uint8_t ReadDataRegu16(uint16_t *u16dat)
{
uint8_t reg,sum;
IIC_Start();
IIC_Send_Byte(0x44); //发送地址
if(IIC_Wait_Ack())return 0;
reg = IIC_Read_Byte(1);//back 0x44
if(reg == 0x88)
{
IIC_Stop(); //产生一个停止条件
return reg;
}
sum = IIC_Read_Byte(1);
*u16dat = IIC_Read_Byte(1);
*u16dat <<= 8;
*u16dat |= IIC_Read_Byte(0);
IIC_Stop(); //产生一个停止条件
if(sum == (uint8_t)((*u16dat >> 8) + (*u16dat&0xff)) )
return reg;
else
return 0;
}
主函数,删了一些无关的,仅供参考
int main(void)
{
volatile uint8_t reg;
uint8_t datA,datB,datC,datD,SUM,ADDSUM,Sendcnt;
uint8_t USARTDMASend;
uint32_t PIRDataValue;
LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_SYSCFG);
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
SystemClock_Config();
MX_GPIO_Init();
// MX_DMA_Init();
MX_USART1_UART_Init();
LL_mDelay(500);
reconfig:
SDA_IN(); //SDA设置为输入 8位机上拉,会有SDA中断信号
Sendcnt = 0;
while(Sendcnt != 0x88)//Config Mode Reg
{
//mode&0xf = 1000 1001 1010 1011
if(LL_GPIO_IsInputPinSet(GPIOA, LL_GPIO_PIN_5) == 0)//SDAINT
{
LL_mDelay(2);//8位机超时前进行下一步
IIC_SCL=0;//拉低时钟准备读取数据
delay_xus(5);//减速以防低速读不到信号
IIC_SCL=1;//准备完成
Sendcnt = SendModeData(0x88|0);//0x88 | 00 MODE
SDA_IN(); //SDA设置为输入
}
}
while (1)
{
if(LL_GPIO_IsInputPinSet(GPIOA, LL_GPIO_PIN_5) == 0)//SDAINT
{
LL_mDelay(2);
IIC_SCL=0;//拉低时钟准备读取数据
delay_xus(5);//减速以防低速读不到信号
IIC_SCL=1;//准备完成
reg = ReadDataRegu16(&Dat.u16PirData);//reg 0x44 data / 0x88 configmode
SDA_IN(); //SDA设置为输入
if(reg == 0x88)
goto reconfig;
else if(reg != 0x44)
continue;
//发送数据
datA=0;//Dat.i16PirData>>24;
datB=0;//Dat.i16PirData>>16;
datC=Dat.i16PirData>>8;
datD=Dat.i16PirData;
// DMA1_MEM_LEN = 10;
USARTDMASEND[0] = 0XAA;
USARTDMASEND[1] = 0xFF;
USARTDMASEND[2] = 0xF1;
USARTDMASEND[3] = 4;
USARTDMASEND[4] = datD;
USARTDMASEND[5] = datC;
USARTDMASEND[6] = datB;
USARTDMASEND[7] = datA;
SUM = 0x9E + datD;
ADDSUM = 0x8B + SUM;
SUM = SUM + datC;
ADDSUM = ADDSUM + SUM;
SUM = SUM + datB;
ADDSUM = ADDSUM + SUM;
SUM = SUM + datA;
ADDSUM = ADDSUM + SUM;
USARTDMASEND[8] = SUM;
USARTDMASEND[9] = ADDSUM;
for(Sendcnt = 0;Sendcnt < 10;Sendcnt++)
USART1SendData(USARTDMASEND[Sendcnt]);
}
}
while (1);//ERR
}
调试主要还是自己要详细画一画时序,对照示波器来进行调整,例程只是提供一种思路。
有些代码也是仿照其他人,综合起来还需自己调整。