移植MODBUS通信实现CS1237(ADC)AD5422(DAC)热电偶(MAX31856)的数据采集

项目需求:使用PCB代替PLC模块进行数据采集

项目目的:降低成本 

模数转换芯片(ADC)CS1237: 通过两线模拟SPI进行通信 通过配置内部寄存器 可进行电压、电流和电阻的数据采集;

数模转换芯片(DAC)AD5422:通过HAL库配置SPI进行通信,通过配置内部寄存器,可进行电压、 电流的输出;

热电偶数字转换器MAX31856:通过HAL库配置SPI进行通信,通过配置内部寄存器,可进行温度的读取。0-1100℃

通信协议:移植freemodbus从机协议使用RS485与PLC进行的数据交互。

菊花链方式:PLC通过访问不同的MODBUS地址进行不同PCB板上所测数据的读取。 

优势:涉及逻辑更加简单,不容易出问题。尤其对于工业需求稳定是第一位。

劣势:受限与modbus的地址数量。

级联模式:因为一整个设备所用很多个测量版,但modbus只有15个地址可用,所以可使用级联模式

如果使用级联模式,那么pcb就需要将modbus主机协议也进行移植,每块pcb板需要至少两个rs485,一个作为从机,一个作为主机。

                                                                图1 级联模式

需要每块PCB既可以作为从机接收数据又可以作为主机读取数据。

级联模式的好处就在于不会受到MODBUS地址数量的限制,可以将所有的地址全部设置为1,PLC只与第一块PCB进行数据交互,之后的所有数据都由modbus一层一层传递到第一块PCB中进行保存。

但是这种模式的劣势也很明显,软件逻辑或者时间没有处理好的话可能会出现很多问题。

如果可以的话更推荐通过CAN进行通信,

CS1237芯片驱动:

需要两根PIN脚来模拟SPI进行通信,且对于延时需要精确到us,因为这个项目对于芯片的性能要求不高 此处为了省事直接使用硬延时;

封装的延时函数

void bsp_DelayUS(uint32_t us)
{
	delay_us(us);
}
void bsp_DelayMS(uint32_t ms)
{
	HAL_Delay(ms);
}

void delay_us(uint32_t us)
{
	for(int i=0; i<100;i++)
	{
    for(; us != 0; us--);
	}
}

模拟SPI进行通信

#define CS1237_SCL_H HAL_GPIO_WritePin(CS1237_SCL_GPIO_Port, CS1237_SCL_Pin, GPIO_PIN_SET)
#define CS1237_SCL_L HAL_GPIO_WritePin(CS1237_SCL_GPIO_Port, CS1237_SCL_Pin, GPIO_PIN_RESET)
 

void DATAIN(void)
{
	 GPIO_InitTypeDef GPIO_InitStruct;

  /*Configure GPIO pin : PF1 */
  GPIO_InitStruct.Pin = GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
}

void DATAOUT(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	  /*Configure GPIO pin : PF1 */
  GPIO_InitStruct.Pin = GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
}



void CS1237_Init_JX(void)
{        
        GPIO_InitTypeDef  GPIO_InitStructure;                                       
                    
        __HAL_RCC_GPIOF_CLK_ENABLE();        

	
        GPIO_InitStructure.Pin = GPIO_PIN_1;               
        GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;                 
        GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;               
        HAL_GPIO_Init(GPIOF, &GPIO_InitStructure);
  
        GPIO_InitStructure.Pin = GPIO_PIN_2;               
        GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;                 
        GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;               
        HAL_GPIO_Init(GPIOF, &GPIO_InitStructure);
 
				
        CS1237_SCL_L;
        CS1237_SDA_L;
}

配置  CS1237芯片

这里的dat是写入到cs1237内部寄存器的值,根据需求(测量电压,电流,或者是电阻)并根据芯片手册去写入相对应的值

//配置CS1237芯片
void CS1237_Config(unsigned char dat)
{
        unsigned char i; 
        unsigned int count_i=0;//溢出计时器  
        DATAOUT();
        CS1237_SDA_H; //OUT引脚拉高
         DATAIN();
        CS1237_SCL_L;// 时钟拉低
        while(CS1237_SDA_READ==1) //等待CS237准备好
        {
                bsp_DelayMS(1);
                count_i++;
                if(count_i > 3000)
                {
                        DATAOUT();
                        CS1237_SDA_H; // OUT引脚拉高
                        CS1237_SCL_H;; // CLK引脚拉高
                        return;//超时,则直接退出程序
                }
        }
        for(i=0;i<29;i++)// 1 - 29
        {
                CS1237_SCL_H;;        // CLK=1;
                bsp_DelayUS(1);
                CS1237_SCL_L;        // CLK=0;
                bsp_DelayUS(1);
        }
        DATAOUT();
        CS1237_SCL_H;;bsp_DelayUS(1);CS1237_SDA_H;CS1237_SCL_L;bsp_DelayUS(1);//30
        CS1237_SCL_H;;bsp_DelayUS(1);CS1237_SDA_H;CS1237_SCL_L;bsp_DelayUS(1);//31
        CS1237_SCL_H;;bsp_DelayUS(1);CS1237_SDA_L;CS1237_SCL_L;bsp_DelayUS(1);//32
        CS1237_SCL_H;;bsp_DelayUS(1);CS1237_SDA_L;CS1237_SCL_L;bsp_DelayUS(1);//33
        CS1237_SCL_H;;bsp_DelayUS(1);CS1237_SDA_H;CS1237_SCL_L;bsp_DelayUS(1);//34
        CS1237_SCL_H;;bsp_DelayUS(1);CS1237_SDA_L;CS1237_SCL_L;bsp_DelayUS(1);//35
        CS1237_SCL_H;;bsp_DelayUS(1);CS1237_SDA_H;CS1237_SCL_L;bsp_DelayUS(1);//36
        //37     写入了0x65
        CS1237_SCL_H;;        // CLK=1;
        bsp_DelayUS(1);
        CS1237_SCL_L;        // CLK=0;
        bsp_DelayUS(1);
        
        for(i=0;i<8;i++)// 38 - 45个脉冲了,写8位数据
        {
                CS1237_SCL_H;;        // CLK=1;
                bsp_DelayUS(1);
                if(dat&0x80)
                        CS1237_SDA_H;// OUT = 1
                else
                        CS1237_SDA_L;
                dat <<= 1;
                CS1237_SCL_L;        // CLK=0;
                bsp_DelayUS(1);
        }
        CS1237_SDA_H;// OUT = 1
        CS1237_SCL_H;;        // CLK=1;
        bsp_DelayUS(1);
        CS1237_SCL_L;        // CLK=0;
        bsp_DelayUS(1);
}

读取芯片的配置数据

这是读取1237内部寄存器的函数 可以写入之后进行读取测试是否写入成功。

unsigned char Read_Config(void)
{
        unsigned char i;
        unsigned char dat=0;//读取到的数据
        unsigned int count_i=0;//溢出计时器
//        unsigned char k=0,j=0;//中间变量
        
        DATAOUT();
        CS1237_SDA_H; //OUT引脚拉高
         DATAIN();
        CS1237_SCL_L;//时钟拉低
        while(CS1237_SDA_READ==1)//等待芯片准备好数据
        {
                bsp_DelayMS(1);
                count_i++;
                if(count_i > 3000)
                {
                        DATAOUT();
                        CS1237_SCL_H;        // CLK=1;
                        CS1237_SDA_H;        // OUT=1;
                        return 1;//超时,则直接退出程序
                }
        }

        for(i=0;i<29;i++)// 产生第1到29个时钟
        {
                CS1237_SCL_H;        // CLK=1;
                bsp_DelayUS(1);;
                CS1237_SCL_L;        // CLK=0;
                bsp_DelayUS(1);;
        }
        
        DATAOUT();
        
        CS1237_SCL_H; // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SDA_H;
        CS1237_SCL_L; // CLK=0;
        bsp_DelayUS(1);;// 这是第30个时钟
        
        CS1237_SCL_H; // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SDA_L;
        CS1237_SCL_L; // CLK=0;
        bsp_DelayUS(1);;// 这是第31个时钟
        
        CS1237_SCL_H; // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SDA_H;
        CS1237_SCL_L; // CLK=0;
        bsp_DelayUS(1);;//32
        
        CS1237_SCL_H; // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SDA_L;
        CS1237_SCL_L; // CLK=0;
        bsp_DelayUS(1);;//33
        
        CS1237_SCL_H; // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SDA_H;
        CS1237_SCL_L; // CLK=0;
        bsp_DelayUS(1);;//34
        
        CS1237_SCL_H; // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SDA_H;
        CS1237_SCL_L; // CLK=0;
        bsp_DelayUS(1);;//35
        
        CS1237_SCL_H; // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SDA_L;
        CS1237_SCL_L; // CLK=0;
        bsp_DelayUS(1);;//36
        
        CS1237_SDA_H;
        CS1237_SCL_H;        // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SCL_L;        // CLK=0;
        bsp_DelayUS(1);;//37     写入0x56 即读命令
        
        dat=0;
         DATAIN();
        for(i=0;i<8;i++)// 第38 - 45个脉冲了,读取数据
        {
                CS1237_SCL_H;        // CLK=1;
                bsp_DelayUS(1);;
                CS1237_SCL_L;        // CLK=0;
                bsp_DelayUS(1);;
                dat <<= 1;
                if(CS1237_SDA_READ==1)
                        dat++;
        }
        
        //第46个脉冲
        CS1237_SCL_H;        // CLK=1;
        bsp_DelayUS(1);;
        CS1237_SCL_L;        // CLK=0;
        bsp_DelayUS(1);;
        
        DATAOUT();
        CS1237_SDA_H; //OUT引脚拉高
        
        return dat;
}

读取ADC数据,返回的是一个有符号数据

int32_t Read_CS1237(void)
{
        unsigned char i;
        uint32_t dat=0;//读取到的数据
        unsigned int count_i=0;//溢出计时器
        int32_t temp;
        
        DATAOUT();
        CS1237_SDA_H; //OUT引脚拉高
         DATAIN();
        CS1237_SCL_L;//时钟拉低
        while(CS1237_SDA_READ==1)//等待芯片准备好数据
        {
                bsp_DelayMS(1);
                count_i++;
                if(count_i > 3000)
                {
                        DATAOUT();
                        CS1237_SCL_H;        // CLK=1;
                        CS1237_SDA_H;        // OUT=1;
                        return 1;//超时,则直接退出程序
                }
        }
        
        dat=0;
        for(i=0;i<24;i++)//获取24位有效转换
        {
                CS1237_SCL_H;        // CLK=1;
                bsp_DelayUS(1);;
                dat <<= 1;
                if(CS1237_SDA_READ==1)
                        dat ++;
                CS1237_SCL_L;        // CLK=0;
                bsp_DelayUS(1);;
        }
        
        for(i=0;i<3;i++)//接着前面的时钟 再来3个时钟
        {
                CS1237_SCL_H;        // CLK=1;
                bsp_DelayUS(1);;
                CS1237_SCL_L;        // CLK=0;
                bsp_DelayUS(1);;
        }
        
        DATAOUT();
        CS1237_SDA_H; // OUT = 1;
        
        if(dat&0x00800000)// 判断是负数 最高位24位是符号位
        {
                temp=-(((~dat)&0x007FFFFF) + 1);// 补码变源码
        }
        else
				{
					temp=dat; // 正数的补码就是源码

				}

			
        return temp;
}

初始化和配置寄存器成功之后 就可以读取ADC的值通过公式换算来计算出你测量的数据。

AD5422驱动:

使用HAL库配置SPI进行通信,通过阅读芯片的手册并研究时序图

AD5422写函数(通过SPI将连续三个字节写入内部寄存器)

参数 add1:是要写的寄存器 根据手册进行填写

        data是要写入的数据

static void uint16_to_uint8(uint16_t value, uint8_t* high_byte, uint8_t* low_byte) {
    *high_byte = (value >> 8) & 0xFF;
    *low_byte = value & 0xFF;
}

static uint16_t AD5422_write(uint8_t add1,uint16_t data)
{
		uint8_t high_byte, low_byte;
    uint8_t tx_buf[3] = {0};
		uint8_t rx_buf[3] = {0};
		uint16_to_uint8(data,&high_byte,&low_byte);
		tx_buf[0]=add1;
		tx_buf[1]=high_byte;
		tx_buf[2]=low_byte;
		HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_RESET);
    HAL_SPI_TransmitReceive(&hspi_ad5422, tx_buf, rx_buf, sizeof(tx_buf), 100);
    HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_SET);		
		delay_us(100);

		return 0;
}

uint16_to_uint8函数是将16位数据转换为两个八位数据,因为这个对于这个芯片写入这种方式更为简单。

复位函数 

复位寄存器为0x56 根据手册,只需要将最低为置为1即可。

static uint16_t AD5422_ret(void)
{
    uint8_t tx_buf[3] = {0};
		uint8_t rx_buf[3] = {0};
		tx_buf[0]=0x56;
		tx_buf[1]=0x00;
		tx_buf[2]=0x01;
		HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_RESET);
    HAL_SPI_TransmitReceive(&hspi_ad5422, tx_buf, rx_buf, sizeof(tx_buf), 100);
    HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_SET);		
		delay_us(100);
		return 0;
}

读函数

这个芯片的读取需要实现一个回读功能, 根据芯片手册的时序图实现这个读函数。

static uint16_t AD5422_Read(uint8_t add1,uint8_t data)
{
		uint8_t high_byte, low_byte;
    uint8_t tx_buf[3] = {0};
		uint8_t rx_buf[3] = {0};
		uint8_t tx_buf_ret[3]={0};
		tx_buf_ret[0]=0X00;
		tx_buf_ret[1]=0X00;
		tx_buf_ret[2]=0X00;
		uint16_to_uint8(data,&high_byte,&low_byte);
		tx_buf[0]=add1;
		tx_buf[1]=high_byte;
		tx_buf[2]=low_byte;
		HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_RESET);
    HAL_SPI_TransmitReceive(&hspi_ad5422, tx_buf, rx_buf, sizeof(tx_buf), 100);
    HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_SET);		
		delay_us(100);
		HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_RESET);	
		HAL_SPI_TransmitReceive(&hspi_ad5422, tx_buf_ret, rx_buf, sizeof(tx_buf), 100);
    HAL_GPIO_WritePin(AD5422_CS_PORT, AD5422_CS_PIN, GPIO_PIN_SET);		
		printf("%x,%x,%x\n",rx_buf[0],rx_buf[1],rx_buf[2]);
		return 0;
}

寄存器的初始化及量程配置

这里我使用了两个5422,只需实现一个即可

init函数配置为0-10v输出或者0-20ma输出其中之一

使用对应的out函数即可进行输出

ad5422_out_0_10v 参数data范围是0-10对应0-10v

d5422_out_0_20ma 参数data范围是0-20对应0-20ma

void ad5422_out_0_10v_init(int addr)
{
	if(addr==0)
	{
			AD5422_ret();
			AD5422_write(0X55,0X7000);
			AD5422_write(0X55,0X7001);

	}
	if(addr==1)
	{
			AD5422_ret_addr2();
			AD5422_write_addr2(0X55,0X7000);
			AD5422_write_addr2(0X55,0X7001);
	}
}

void ad5422_out_0_10v(int addr,float data)
{
		uint16_t temp=0;
		temp = data*6068.05;
		if(addr==0)
		{
			AD5422_write_addr2(0X01,temp);		//对应模拟输出2
		}
		if(addr==1)
		{
			AD5422_write(0X01,temp);		//对应模拟输出2
		}
		
}

void ad5422_out_0_20ma_init(int addr)
{
	if(addr==0)
	{
			AD5422_ret();
			AD5422_write(0X55,0X4000);
			AD5422_write(0X55,0X4006);
			AD5422_write(0X55,0X5006);
	}
	if(addr==1)
	{
			AD5422_ret_addr2();
			AD5422_write_addr2(0X55,0X4000);
			AD5422_write_addr2(0X55,0X4006);
			AD5422_write_addr2(0X55,0X5006);
	}
}

void ad5422_out_0_20ma(int addr,float data)
{
		uint16_t temp=0;
		temp =data/20*65535;
		if(addr==0)
			AD5422_write(0X01,temp);
		if(addr==1)
			AD5422_write_addr2(0X01,temp);
}

MAX31856

使用HAL库配置SPI进行通信

写寄存器 和读寄存器函数 reg的值不懂可以看芯片手册有详细介绍

// 写寄存器
void MAX31856_WriteReg(uint8_t reg, uint8_t data) {
  reg |= 0x80; // 设置写标志位(根据MAX31856协议)
  
  HAL_GPIO_WritePin(MAX31856_CS_PORT, MAX31856_CS_PIN, GPIO_PIN_RESET);
  HAL_SPI_Transmit(&hspi1, &reg, 1, 100);
  HAL_SPI_Transmit(&hspi1, &data, 1, 100);
  HAL_GPIO_WritePin(MAX31856_CS_PORT, MAX31856_CS_PIN, GPIO_PIN_SET);
}
// 读寄存器
uint8_t MAX31856_ReadReg(uint8_t reg) {
  uint8_t rx_data;
  
  HAL_GPIO_WritePin(MAX31856_CS_PORT, MAX31856_CS_PIN, GPIO_PIN_RESET);
  HAL_SPI_Transmit(&hspi1, &reg, 1, 100);
  HAL_SPI_Receive(&hspi1, &rx_data, 1, 100);
  HAL_GPIO_WritePin(MAX31856_CS_PORT, MAX31856_CS_PIN, GPIO_PIN_SET);
  
  return rx_data;
}

初始化函数

此芯片也使用了两个,只实现一个即可

void MAX31856_Init(int addr) 
{
	if(addr==0)							//SPI3对应热电偶1
	{
		MAX31856_WriteReg_ADDR1(0x01, 0x33); // 连续转换模式,50Hz滤波
		MAX31856_WriteReg_ADDR1(0x00, 0xD0); // 选择K型热电偶(具体值参考数据手册)
		
	}
		if(addr==1)							//SPI1对应热电偶2
	{
		MAX31856_WriteReg(0x01, 0x33); // 连续转换模式,50Hz滤波
		MAX31856_WriteReg(0x00, 0xD0); // 选择K型热电偶(具体值参考数据手册)
	}

}

读取温度函数

float MAX31856_ReadTemp(void) {  
	
  uint8_t temp[3];
	uint32_t raw_temp=0;
	float tem;
  MAX31856_ReadReg(0x0C); // 
  uint8_t reg = 0x0C;  
  HAL_GPIO_WritePin(MAX31856_CS_PORT, MAX31856_CS_PIN, GPIO_PIN_RESET);
  HAL_SPI_Transmit(&hspi1, &reg, 1, 100);
  HAL_SPI_Receive(&hspi1, temp, 3, 100);
  HAL_GPIO_WritePin(MAX31856_CS_PORT, MAX31856_CS_PIN, GPIO_PIN_SET);
  raw_temp = (temp[0] << 16) | (temp[1] << 8) | temp[2];
  raw_temp >>= 5; // 
  tem=raw_temp*0.0078125;
  return  tem; // 
}

对于内部不懂的参数,可先了解相关手册后进行使用

原本此项目使用的是级联模式,之后领导要求使用菊花链方式,上层逻辑部分不复杂,这里就不赘述。

对于modbus从机的移植可观看我之前写的博客。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值