旧乡故客
发表于: 2018-8-3 10:13:44 | 显示全部楼层

本篇文章主要介绍了EFM8微控制器与DAC和MAX31855进行通信的固件代码。

该PID(比例 - 积分 - 微分)温度控制系统的主要组件包含EFM8微控制器、DAC和MAX31855热电偶数字转换器。该系列总共有6部分:

●    Part 1:电路原理设计

●    Part 2:板级集成


硬件设计

在上一篇文章中,我们介绍了PID控制系统的示意图:

PID2_diagram1.JPG


从该图中我们可以看到建立PID系统需要三个基本组件:执行PID计算的控制器;将控制器的计算转化为物理变化的执行器;以及反馈机制,将物理变化转换回模拟或数字信号,可用于控制器的计算。在我们的PID恒温器中,这三个组件分别对应于EFM8微控制器、带大电流驱动电路的DAC以及MAX31855热电偶数字转换器。因此,项目的成功取决于是否有可靠的通信,能够将这三个功能块集成到一个统一的PID系统中。


SPI通信

实际上,SPI表示串行外设接口(Serial Peripheral Interface)。SPI是一种简单、灵活、低资源的通信协议,市场上的每个微控制器和DSP都可以支持这种协议。而且,幸运的是,MAX31855和DAC都支持SPI。但是,SPI不是固定的、精确定义的协议;它更像是一个串行通信的通用方法。标准功能如下:

●    总线上的每个设备都是主机设备或从机设备。 (可能有多个主设备,但我建议尽可能避免这种情况。对于多主机系统,最好选择I2C。)

●    电气连接包括串行时钟信号(SCK)、主机到从机数据信号(MOSI,即主机输出从机输入)、从机到主机数据信号(MISO,即主机输入从机输出)和每个从设备的一个从机选择信号(NSS)。

●    主机通过激活一条或多条NSS连接线以及在SCK上驱动时钟脉冲来启动所有事务。只要NSS处于活动状态,事务就会继续。

●    事务可以涉及从主设备到从设备(通过MOSI)、从从设备到主设备(通过MISO)或两者的数据传输。主设备可以通过激活所有NSS线路将数据发送到多个从机设备,但为了避免驱动程序争用,从设备必须采用一些共享MISO信号的方法。


以下原理图显示了SPI总线上器件的硬件设计。

PID2_schem1_2.jpg


精确的传输时间

以下两张图展示了总线上两个从设备的SPI时序的详细信息。第一张来自MAX31855的数据手册,第二张来自于DAC的数据手册。

PID2_datasheet1.JPG

PID2_datasheet2.JPG

在MAX31855的图中,使用CS代替NSS。 CS表示“芯片选择(chip select)”,上划线表示其有效状态为逻辑低电平;NSS代表“非从机选择(not slave select)”,其中“非”(而不是采用上划线)表示它是低电平有效信号。


在DAC的图中,使用带有上划线的SYNC而不是NSS,但在这种情况下情况稍微复杂一些。 DAC旨在兼容各种串行通信协议;这就是为什么时序图看起来不像典型的SPI。但典型的SPI工作正常,这就是我们将要使用的时序。


如果你仔细观察这两个图,你会发现一个小但重要的差异:对于MAX31855,NSS下降沿之后的第一个时钟转换是从低到高的转换。而对于DAC,它是一个从高到低的转换:

PID2_datasheet3.jpg


这意味着我们需要根据正在与哪个从机通信来调整EFM8的SPI配置。下图说明了EFM8控制器的SPI外设提供的四种时钟配置选项;只需将设备的时序详细信息与此图进行比较,即可确定正确的配置。

PID2_datasheet4.jpg


组合数据

SPI是一种可延展的协议,这不仅适用于时序细节,也适用于数据格式化。 MAX31855将数据作为一个32位字传输,如下所示:

PID2_datasheet5.jpg


我们不用担心这个项目的错误处理,不需要知道MAX31855的内部温度,因此我们需要的所有信息都包含在前14位(位31到位18)中。但是,EFM8的SPI外设以字节为单位工作,因此我们将从MAX31855读取两个字节,然后忽略两个最低有效位。


从MAX31855读取数据的过程从一个名为GatherTempData()的函数开始,然后是一个结合到SPI中断服务程序中的简单状态机。以下代码摘录中的注释和描述性标识符可帮助您了解MAX31855接口的固件详细信息。

  1. void GatherTempData(void)
  2. {
  3.         //ensure that we are not interrupting an ongoing transmission
  4.         while(SPI_State != IDLE);

  5.         SPI0CN0_SPIEN = 0;        //disable SPI so we can change the clock configuration
  6.         SPI0CFG &= ~SPI0CFG_CKPOL__BMASK;     //set clock phase for the MAX31855 interface
  7.         SPI0CFG &= ~SPI0CFG_CKPHA__BMASK;     //set clock polarity for the MAX31855 interface
  8.         SPI0CN0_SPIEN = 1;        //re-enable SPI

  9.         TC_NSS = LOW;        //activate MAX31855 slave select

  10.         /*We need to write a dummy byte to initiate the SPI transaction.
  11.          * We do not need to send any data to the MAX31855; rather,
  12.          * writing to the SPI0DAT register forces the EFM8 to generate
  13.          * the clock pulses that cause the MAX31855 to transmit data.*/
  14.         SPI0DAT = 0x00;

  15.         SPI_State = FIRST_TC_BYTE_SENT;
  16. }
复制代码
  1. SI_INTERRUPT (SPI0_ISR, SPI0_IRQn)
  2. {
  3.         //SPI registers are on all SFR pages, so need need to modify SFRPAGE

  4.         SPI0CN0_SPIF = 0;        //clear interrupt flag

  5.         switch(SPI_State)
  6.         {
  7.                 //SPI communications with thermocouple IC====================================
  8.                 case FIRST_TC_BYTE_SENT:
  9.                 RawTempData = SPI0DAT;
  10.                 SPI0DAT = 0x00;      //write a second dummy byte so that the MAX31855 will continue transmitting
  11.                 SPI_State = SECOND_TC_BYTE_SENT;
  12.                 break;

  13.                 case SECOND_TC_BYTE_SENT:
  14.                 TC_NSS = HIGH;      //disable slave select

  15.                 RawTempData = (RawTempData << 8) | SPI0DAT;

  16.                 /* The following instructions convert the raw
  17.                  * binary temperature data into a format that
  18.                  * both Scilab and the EFM8 can easily convert
  19.                  * into a floating point number.*/
  20.                 TempData[0] = LOWBYTE(RawTempData >> 12);
  21.                 TempData[1] = LOWBYTE(RawTempData >> 4);
  22.                 switch((RawTempData & (BIT3|BIT2)) >> 2)
  23.                 {
  24.                         case 0: TempData[2] = 0;
  25.                         break;

  26.                         case 1: TempData[2] = 25;
  27.                         break;

  28.                         case 2: TempData[2] = 50;
  29.                         break;

  30.                         case 3: TempData[2] = 75;
  31.                         break;
  32.                 }
  33.                 TEMP_DATA_READY = TRUE;
  34.                 SPI_State = IDLE;
  35.                 break;

  36.                 //SPI communications with DAC===========================================
  37.                 case FIRST_DAC_BYTE_SENT:
  38.                 SPI0DAT = UpdateDAC_SecondByte;
  39.                 SPI_State = SECOND_DAC_BYTE_SENT;
  40.                 break;

  41.                 case SECOND_DAC_BYTE_SENT:
  42.                 DAC_NSS = HIGH;      //disable slave select
  43.                 SPI_State = IDLE;
  44.                 break;
  45.         }
  46. }
复制代码

DAC传输的是一个16位字长,如下所示:

PID2_datasheet6.jpg


我们将发送两个连续的字节,DAC将其认为为一个16位字。 加热器驱动电路连接到DAC通道D,因此前两位将是二进制11。我们项目采用的正确更新模式是“写入指定寄存器并更新输出(write to specified register and update outputs)”,因此第三和第四位将是二进制01。与MAX31855接口一样,DAC事务以UpdateDAC()函数开始,然后是SPI状态机。

  1. void UpdateDAC(unsigned char ChannelABCorD, unsigned char DACcode)
  2. {
  3.         //ensure that we are not interrupting an ongoing transmission
  4.         while(SPI_State != IDLE);

  5.         /*This switch statement sets the two most significant bits of the 16-bit DAC word
  6.          * according to which channel is being updated. It also sets the two "operation mode"
  7.          * bits to binary 01, which corresponds to "write to specified register and
  8.          * update outputs."*/
  9.         switch(ChannelABCorD)
  10.         {
  11.                 case DAC_CH_A:
  12.                         UpdateDAC_FirstByte = 0x10;
  13.                         break;

  14.                 case DAC_CH_B:
  15.                         UpdateDAC_FirstByte = 0x50;
  16.                         break;

  17.                 case DAC_CH_C:
  18.                         UpdateDAC_FirstByte = 0x90;
  19.                         break;

  20.                 case DAC_CH_D:
  21.                         UpdateDAC_FirstByte = 0xD0;
  22.                         break;
  23.         }

  24.         /*The upper four bits of the DAC code are the lower four bits
  25.          * of the first byte, and the lower four bits of the DAC code are
  26.          * the upper four bits of the second byte.*/
  27.         UpdateDAC_FirstByte = UpdateDAC_FirstByte | (DACcode >> 4);
  28.         UpdateDAC_SecondByte = DACcode << 4;

  29.         SPI0CN0_SPIEN = 0;        //disable SPI so we can change the clock configuration
  30.         SPI0CFG |= SPI0CFG_CKPOL__BMASK;        //set clock phase for the DAC interface
  31.         SPI0CFG &= ~SPI0CFG_CKPHA__BMASK;        //set clock polarity for the DAC interface
  32.         SPI0CN0_SPIEN = 1;        //re-enable SPI

  33.         DAC_NSS = LOW;      //activate DAC slave select
  34.         SPI0DAT = UpdateDAC_FirstByte;
  35.         SPI_State = FIRST_DAC_BYTE_SENT;
  36. }
复制代码

固件

以下只是一个测试程序,通过反复递增加载到DAC通道D的8位值,将加热器驱动电压从0 V变化到大约2.4 V:

  1. int main(void)
  2. {
  3.         unsigned char Heater_Drive;

  4.         //call hardware initialization routine
  5.         enter_DefaultMode_from_RESET();

  6.         //enable global interrupts
  7.         IE_EA = 1;

  8.         Heater_Drive = 0;

  9.         //refer to the PCA ISR for information on the delay functionality
  10.         PCA0 = 0x0000 - PID_INTERVAL;
  11.         PCA0CN0_CR = PCA_RUN;

  12.         while (1)
  13.         {
  14.                 PID_WAIT = TRUE;
  15.                 while(PID_WAIT == TRUE);

  16.                 UpdateDAC(DAC_HEATER, Heater_Drive);
  17.                 Heater_Drive++;
  18.         }
  19. }
复制代码

DAC更新之间的延迟 - 称为“PID间隔”,因为我们将使用与实际PID控制器相同的延迟功能 - 通过将Timer0配置为以1 kHz的频率溢出来实现,然后使用Timer0溢出作为可编程计数器阵列(PCA)的时钟源,使得16位PCA计数器/定时器寄存器每毫秒递增一次。

  1. SI_INTERRUPT (PCA0_ISR, PCA0_IRQn)
  2. {
  3.         //PCA registers are on all SFR pages, so no need to modify SFRPAGE

  4.         PCA0CN0_CF = 0;        //clear interrupt flag

  5.         /*The overflow interrupt fires when the PCA counter/timer
  6.          * overflows from 0xFFFF to 0x0000, so we create the delay
  7.          * as follows:*/
  8.         PCA0 = 0x0000 - PID_INTERVAL;        //PID_INTERVAL is the delay in milliseconds

  9.         PID_WAIT = FALSE;
  10. }
复制代码

总结

现在我们已经将三个PID组件转变为一个不会停止的的、无缝集成的温度控制系统。 在下一篇文章中,我们将实现一个简单的嵌入式PID算法,并使用示波器测量和可变强度LED来观察系统的功能。

PID2_scope1.jpg
跳转到指定楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 29 | 回复: 32



手机版|

GMT+8, 2024-11-21 18:27 , Processed in 0.037170 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表