【沁恒CH582】 I2C暂存的继电器上位机控制继电器模块
<h2 cid="n134" mdtype="heading">i2c接口与继电器驱动</h2><p cid="n135" mdtype="paragraph">本次我们将要实现的功能是上位机发送继电器控制指令(单字节控制指令),24C256EEPROM用于保存当前的继电器状态,并且在保存之后驱动继电器处于正确的状态。</p>
<p cid="n152" mdtype="paragraph">接下来我们逐步完成。首先我们需要选定一个基础例程,在此基础上进行改动,我计划选用IIC例程进行开发。</p>
<h3 cid="n154" mdtype="heading">对于IIC例程做基础分析加工</h3>
<p cid="n157" mdtype="paragraph">首先测试是否能正确烧写例程。链接硬件如下:</p>
<p cid="n169" mdtype="paragraph"> </p>
<p cid="n159" mdtype="paragraph">就是为开发板连上编程器和串口转换器,然后链接一个24C256的EEPROM。(目前手上最方便的就是这块测试板了)。</p>
<p cid="n161" mdtype="paragraph">然后编译例程,并烧录。</p>
<p cid="n163" mdtype="paragraph"><img alt="image-20220410141732107" data-local-refresh="true" onerror="onImageErrorFunc(event)" onload="onLoadedFuncForQuickAction(event)" referrerpolicy="no-referrer" src="file://G:\OneDriveSync\OneDrive%20-%20zju.edu.cn\_Notes\LocalPictureService\image-20220410141732107-16495714549841.png?lastModify=1649578377" /></p>
<p cid="n165" mdtype="paragraph">接下来的问题就是确定数据保存的位置和代码中对于数据定位的部分调整了。</p>
<p cid="n171" mdtype="paragraph">接下来我们先做一个简单的测试,我们使用单字节写入的模式对芯片的第一个地址空间和最后一个地址空间分别写入0x56和0x78,然后读出这两个地址空间的数据并发送到上位机中。</p>
<p cid="n173" mdtype="paragraph">首先是器件地址,根据数据手册中给出的描述10100A_1A_0\,b我们将A_1,A_0连接到低电平,然后我们可以得到器件地址为1010000\,B=0x50。</p>
<p cid="n175" mdtype="paragraph">接下来将需要向器件中输入16bits的地址,之后紧跟8bits的具体数据,最终发送停止位。</p>
<p cid="n179" mdtype="paragraph">按照我们的定义,我们将要发送<code> 0x00 0x00 0x56 </code>,<code> 0xFF 0xFF 0x78 </code>,我们知道256k的EEPROM的首地址应当为<code>0x0000</code>末地址为<code>0xFFFF</code>。那么按照这个逻辑可以得到对应的代码。</p>
<pre cid="n181" lang="C++" mdtype="fences" spellcheck="false">
void send_one_byte(uint8_t data)
{
I2C_SendData(data);
while(I2C_GetFlagStatus(I2C_FLAG_TXE) != RESET);
}
void main()
{
// ... 初始化部分
I2C_GenerateSTART(ENABLE);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(0x50, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
send_one_byte(0x00);
send_one_byte(0x00);
send_one_byte(0x56);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(ENABLE);
I2C_GenerateSTART(ENABLE);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(0x50, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
send_one_byte(0xff);
send_one_byte(0xff);
send_one_byte(0x78);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(ENABLE);
// ... 后续循环部分
}</pre>
<p cid="n183" mdtype="paragraph">接下来我们应该读取这两个地址的内容并发送到上位机。</p>
<p cid="n185" mdtype="paragraph">根据数据手册上的说明这款芯片的接收时序是这样的:</p>
<p cid="n187" mdtype="paragraph"><img alt="image-20220410144159159" data-local-refresh="true" onerror="onImageErrorFunc(event)" onload="onLoadedFuncForQuickAction(event)" referrerpolicy="no-referrer" src="file://G:\OneDriveSync\OneDrive%20-%20zju.edu.cn\_Notes\LocalPictureService\image-20220410144159159.png?lastModify=1649578377" /></p>
<p cid="n150" mdtype="paragraph">那么我们可以啰嗦地,写出逐个字节读取地代码。</p>
<pre cid="n199" lang="C++" mdtype="fences" spellcheck="false">
I2C_GenerateSTART(ENABLE);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(0x50, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
send_one_byte(0xff);
send_one_byte(0xff);
send_one_byte(0x78);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(ENABLE);
printf("Data has been sent!\r\n");
I2C_GenerateSTART(ENABLE);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(0x50, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
send_one_byte(0x00);
send_one_byte(0x00);
send_one_byte(0xff);
send_one_byte(0xff);
I2C_GenerateSTART(ENABLE); // 重复产生起始位
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED));
printf("Addr:0x0000, Data: 0x%x",I2C_ReceiveData());
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED));
printf("Addr:0xFFFF, Data: 0x%x",I2C_ReceiveData());
</pre>
<p cid="n201" mdtype="paragraph">有了这一段代码可以验证是否数据被正确存入指定区域。</p>
<h3 cid="n208" mdtype="heading">UART接收中断</h3>
<p cid="n223" mdtype="paragraph">首先是初始化代码:</p>
<pre cid="n210" lang="c++" mdtype="fences" spellcheck="false">
GPIOA_SetBits(GPIO_Pin_9);
GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA);
UART1_DefInit();
UART1_ByteTrigCfg( UART_1BYTE_TRIG );
trigB = 1;
UART1_INTCfg( ENABLE, RB_IER_RECV_RDY|RB_IER_LINE_STAT );
PFIC_EnableIRQ( UART1_IRQn );
</pre>
<p cid="n221" mdtype="paragraph">之后是中断响应函数:</p>
<pre cid="n224" lang="C++" mdtype="fences" spellcheck="false">
__INTERRUPT
__HIGH_CODE
void UART1_IRQHandler( void )
{
UINT8V i;
switch ( UART1_GetITFlag() )
{
case UART_II_LINE_STAT : // 线路状态错误
{
UART1_GetLinSTA();
break;
}
case UART_II_RECV_RDY : // 数据达到设置触发点
for ( i = 0; i != trigB; i++ )
{
RxBuff = UART1_RecvByte();
UART1_SendByte( RxBuff );
}
break;
case UART_II_RECV_TOUT : // 接收超时,暂时一帧数据接收完成
i = UART1_RecvString( RxBuff );
UART1_SendString( RxBuff, i );
break;
case UART_II_THR_EMPTY : // 发送缓存区空,可继续发送
break;
case UART_II_MODEM_CHG : // 只支持串口0
break;
default :
break;
}
}</pre>
<p cid="n226" mdtype="paragraph">这样我们就得到了一个ECHO例程。</p>
<p cid="n213" mdtype="paragraph"> </p>
<h3 cid="n209" mdtype="heading">继电器模块</h3>
<p cid="n206" mdtype="paragraph">我们假设输入A-D表示吸合1-4继电器,那么就是在GPIOA1-GPIO4执行输出。E-H表示关闭GPIOA1-GPIOA4。我们可以魔改一下上面的程序。</p>
<p cid="n215" mdtype="paragraph">将GPIO 设为低电流推挽输出,<code>GPIO_ModeOut_PP_5mA</code>。</p>
<pre cid="n217" lang="c++" mdtype="fences" spellcheck="false">
__INTERRUPT
__HIGH_CODE
void UART1_IRQHandler( void )
{
UINT8V i;
switch ( UART1_GetITFlag() )
{
case UART_II_LINE_STAT : // 线路状态错误
{
UART1_GetLinSTA();
break;
}
case UART_II_RECV_RDY : // 数据达到设置触发点
for ( i = 0; i != trigB; i++ )
{
RxBuff = UART1_RecvByte();
UART1_SendByte( RxBuff );
switch(RxBuff)
{
case 'A':
GPIOA_SetBits(GPIO_Pin_1);
break;
case 'B':
GPIOA_SetBits(GPIO_Pin_2);
break;
case 'C':
GPIOA_SetBits(GPIO_Pin_3);
break;
case 'D':
GPIOA_SetBits(GPIO_Pin_4);
break;
case 'E':
GPIOA_ResetBits(GPIO_Pin_1);
break;
case 'F':
GPIOA_ResetBits(GPIO_Pin_2);
break;
case 'G':
GPIOA_ResetBits(GPIO_Pin_3);
break;
case 'H':
GPIOA_ResetBits(GPIO_Pin_4);
break;
default:
break;
}
//UART1_SendByte(RxBuff);
}
break;
case UART_II_RECV_TOUT : // 接收超时,暂时一帧数据接收完成
i = UART1_RecvString( RxBuff );
UART1_SendString( RxBuff, i );
switch(RxBuff)
{
case 'A':
GPIOA_SetBits(GPIO_Pin_1);
break;
case 'B':
GPIOA_SetBits(GPIO_Pin_2);
break;
case 'C':
GPIOA_SetBits(GPIO_Pin_3);
break;
case 'D':
GPIOA_SetBits(GPIO_Pin_4);
break;
case 'E':
GPIOA_ResetBits(GPIO_Pin_1);
break;
case 'F':
GPIOA_ResetBits(GPIO_Pin_2);
break;
case 'G':
GPIOA_ResetBits(GPIO_Pin_3);
break;
case 'H':
GPIOA_ResetBits(GPIO_Pin_4);
break;
default:
break;
}
break;
case UART_II_THR_EMPTY : // 发送缓存区空,可继续发送
break;
case UART_II_MODEM_CHG : // 只支持串口0
break;
default :
break;
}
}</pre>
<p cid="n193" mdtype="paragraph">利用万用表可以测量得到这种情况下,低电平为0V,高电平为5.12V.</p>
<h3 cid="n195" mdtype="heading">缝合!</h3>
<p cid="n228" mdtype="paragraph">将以上代码,缝合到一起,让我们每次的继电器操作结果能够保存在EEPROM中。可以用下面的代码实现:</p>
<p cid="n233" mdtype="paragraph">首先是初始化继电器初始状态:</p>
<pre cid="n229" lang="C++" mdtype="fences" spellcheck="false">
uint8_t state;
I2C_GenerateSTART(ENABLE);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(0x50, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
send_one_byte(0x00);
send_one_byte(0x00);
I2C_GenerateSTART(ENABLE); // 重复产生起始位
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_RECEIVED));
state = I2C_ReceiveData();
printf("Addr:0x0000, Data: 0x%x",state); // 初始化</pre>
<p cid="n231" mdtype="paragraph">接下来是更新中断响应函数:</p>
<pre cid="n237" lang="c++" mdtype="fences" spellcheck="false">
__INTERRUPT
__HIGH_CODE
void UART1_IRQHandler( void )
{
UINT8V i;
switch ( UART1_GetITFlag() )
{
case UART_II_LINE_STAT : // 线路状态错误
{
UART1_GetLinSTA();
break;
}
case UART_II_RECV_RDY : // 数据达到设置触发点
for ( i = 0; i != trigB; i++ )
{
RxBuff = UART1_RecvByte();
UART1_SendByte( RxBuff );
switch(RxBuff)
{
case 'A':case 'B':case 'C':case 'D':
state |= 1<<(RxBuff - 'A');
break;
case 'E':case 'F':case 'G':case 'H':
state &= 0xFF^1<<(RxBuff - 'A');
break;
default:
break;
}
}
break;
case UART_II_RECV_TOUT : // 接收超时,暂时一帧数据接收完成
i = UART1_RecvString( RxBuff );
UART1_SendString( RxBuff, i );
switch(RxBuff)
{
case 'A':case 'B':case 'C':case 'D':
state |= 1<<(RxBuff - 'A');
break;
case 'E':case 'F':case 'G':case 'H':
state &= 0xFF^1<<(RxBuff - 'A');
break;
default:
break;
}
}
break;
case UART_II_THR_EMPTY : // 发送缓存区空,可继续发送
break;
case UART_II_MODEM_CHG : // 只支持串口0
break;
default :
break;
}
R32_PA_OUT = state << 1; // 从A1开始
}</pre>
<p cid="n239" mdtype="paragraph">至此我们就实现了一个可以由上位机控制的继电器组了!虽然我们只是用万用表测量了一下输出情况,但是已经能够起到我们预想中的效果了。</p>
<p>文中图片没上传上来</p>
<p>补一张硬件连接图,手头东西比较简陋,笑死,但是好使就行! </p>
<p>将GPIO 设为低电流推挽输出,</p>
<ul>
<li>GPIO_ModeOut_PP_5mA,</li>
</ul>
<p>如果你要想推动继电器的话,建议你这里设置为20mA,我原来用这个推动固态继电器,推动不了。</p>
<p>还有我网页版看不到你的图片。你是上传world文档,还是直接编辑的。有空的话,修正一下,谢谢。</p>
lugl4313820 发表于 2022-4-13 18:58
将GPIO 设为低电流推挽输出,
GPIO_ModeOut_PP_5mA,
如果你要想推动继电器的话,建议你这里设 ...
<p>现在一般继电器模块之前都有光耦的,所以功率问题不大,5mA驱动光耦是足够的。可能是我没有交代清楚,抱歉。</p>
javnson 发表于 2022-4-14 23:41
现在一般继电器模块之前都有光耦的,所以功率问题不大,5mA驱动光耦是足够的。可能是我没有交代清楚,抱 ...
<p>我的是直接驱动固态继电器,所以对电流可能要求高一些,曾经排查好些时间,提示一下而。</p>
<p>其实你可以买个几块钱的继电器模块,这样展示就更加具有说服力。</p>
<p>确实,只是现在物流实在让人崩溃,哈哈哈哈哈哈哈哈哈,等以后都会有的!</p>
lugl4313820 发表于 2022-4-15 06:45
我的是直接驱动固态继电器,所以对电流可能要求高一些,曾经排查好些时间,提示一下而。
<p>谢谢呀~哈哈哈哈哈,看来都是踩了不少坑的兄弟</p>
javnson 发表于 2022-4-15 23:33
谢谢呀~哈哈哈哈哈,看来都是踩了不少坑的兄弟
<p>还没有休息呀,我也是刚刚调完程序,发了帖,后面两天没空调了。</p>
<p>其实数据不多的话,可以放在flash里,停电是保存。</p>
<p>不知道这个芯片有没有PVD,用这个中断来触发保存数据。stm32是有这个功能的。</p>
<p>可以用内部电池电压的检测来触发储存,电压低于某个值时保存状态。上电时取出状态。</p>
lugl4313820 发表于 2022-4-15 23:42
可以用内部电池电压的检测来触发储存,电压低于某个值时保存状态。上电时取出状态。
<p>用IIC在平时工作过程中还有一个好处,就是可以指定初始条件,焊一个有初始条件要求的IIC芯片上去,要是不满足初始条件直接罢工,哈哈哈哈哈,不过您说的直接保存在本地FLASH的方法确实是一个更加简便的方法!</p>
<p>这款芯片也提供了一个FLASH操作的库,很方便的,之后工程应用的时候我去试试</p>
javnson 发表于 2022-4-18 09:19
用IIC在平时工作过程中还有一个好处,就是可以指定初始条件,焊一个有初始条件要求的IIC芯片上去,要是不 ...
<p>牛了,有想法呀。。。不交钱不给开机。。。这想法好!</p>
页:
[1]