Funpack2-4:基于AVR64DD32实现一个恒温自动控制系统
使用微芯公司的AVR64DD32实现一个恒温自动控制系统,可设置温度,并自动控制实现稳定在±2°C
标签
嵌入式系统
Funpack活动
测试
显示
游泳的鸟儿
更新2023-05-05
福州大学
700

一.项目描述

项目需求:

      IO扩展板上有一处加温电阻,将加热区域用物体(纸巾等)包裹起来,通过电流给电阻加热,并通过温度传感器感知板上温度的变化,测温以及在LCD屏上的温度显示。

      要求:使用按键设定目标温度,并且通过程序控制加热功率,使得温度尽快尽量稳定的维持在目标温度。温度偏离设定温度±3°C彩灯变为红色。(注意,加热电阻满占空比开启后温度较高)

   本人已全部实现。

FqPq5Jy4tiWwVNWHBXsi-oIEo0w9

开发板简介:

      AVR DD系列是Microchip公司AVR单片机产品线中的最新的一员,它的内部结构灵活、性能强大,有着比较丰富的外设和存储资源。本次申请参加Funpack活动,是为了体验该芯片上差分ADC,过零检测硬件ZCD、逻辑资源等。这些功能在我目前掌握的单片机中很少出现,所以趁此机会学习一下这款AVR的单片机。

二.设计思路

开发板、拓展板

FtRiWI_mMhenkz-vWLpVz-NQ5eFm

如上是本次avr单片机的核心板外围引脚电路图

搭配数字系统的输入、输出扩展板

这个是本次搭配的拓展板,二者通过杜邦线进行连接。

②按键检测原理

FsH9s9qCDRkLxAbgXGLvEvQyqkfi

如上为按键部分原理图。通过一个io口输出数字量从而读取按键组合。该电路图上有100k和200k电阻,让我想到了R2R的dac网络结构。那么根据R2R的原理,五个按键可以当作五个开关,那么就会有5bit分辨率的电压输出。而我们采集的时候是用adc进行采集,adc在单片机中本质也是二进制,所以我选择只用avr单片机中连续采样10位adc单端模式,然后取出高五位,那么每一位的0或1结果就代表着对应的按键,从而可以识别出哪一个按键按下了。因为adc无法使用中断,所以在主程序循环中使用查询法。

static void ADC0_init(void)
{
    /* Select ADC voltage reference */
    VREF.ADC0REF = VREF_REFSEL_VDD_gc;
    
    /* Disable digital input buffer */
    PORTF.PIN2CTRL &= ~PORT_ISC_gm;
    PORTF.PIN2CTRL |= PORT_ISC_INPUT_DISABLE_gc;
    
    /* Disable pull-up resistor */
    PORTF.PIN2CTRL &= ~PORT_PULLUPEN_bm;

    ADC0.CTRLC = ADC_PRESC_DIV4_gc;      /* CLK_PER divided by 4 */
    
    /* Select ADC channel */
    ADC0.MUXPOS  = ADC_MUXPOS_AIN18_gc;
    
    ADC0.CTRLA = ADC_ENABLE_bm          /* ADC Enable: enabled */
               | ADC_RESSEL_10BIT_gc    /* 10-bit mode */
               | ADC_FREERUN_bm;        /* Enable FreeRun mode *///自动开启下一次转换
}

static uint16_t ADC0_read(void)
{
    /* Clear the interrupt flag by writing 1: */
    ADC0.INTFLAGS = ADC_RESRDY_bm;
    
    return ADC0.RES;
}

static void ADC0_start(void)
{
    /* Start conversion */
    ADC0.COMMAND = ADC_STCONV_bm;
}

static bool ADC0_conversionDone(void)
{
    return (ADC0.INTFLAGS & ADC_RESRDY_bm);
}

↑ADC的配置

        if (ADC0_conversionDone())
        {
            adcVal = ADC0_read();
           a1=adcVal&0x200;
           a2=adcVal&0x100;          
        }
        if(a1){set_temp++;}
        if(a2){set_temp--;}

在while循环里的检测按键,a1和a2就是取ad的最高位和次高位,从而读取按键

③NST112的读取

   通过数据手册可以看到NST112是使用i2c协议进行通信的。那么本次我使用io口模拟i2c的方式,没有使用硬件i2c,因为读温度数据短,不需要很高速度,用软件i2c便于移植。

FiTQHkY2YD16VzfLqg1QvgioLand

   在板子上A0脚连接GND了,所以设备地址是1001000,所以设备地址为0x48,我程序里定义的需要左移以为,所以是0x90。

FiD8Ni56oKbgsQC0prga4oH6B27H

   寄存器地址只需要0x00配置寄存器,在初始化的时候使用,0x01为读温度寄存器,每次采集温度的时候访问0x01读两个字节

FvHgQbUoyAvYUN7KwitiRHOkBxjb

   这个就是0x00配置寄存器,在初始化的时候发两个字节配置一下即可,我设置8Hz采样

void NST112_configuration(){
    char hh[2]={0x60,0xE0}; //8Hz
    I2C_SAND_BUFFER(0x90, 0x01,hh,2);
}

   ↑初始化程序部分

        I2C_READ_BUFFER(0x90,0x00,buf,2);
         now_T=buf[0]*256+buf[1];
         now_T=now_T>>4;

   ↑读取温度和进行数据拼接和移位

④加热的控制

   由于加热电阻在pcb的下方,温度传感器在pcb的上方,所以检测温度会比实际温度滞后,造成很大的过冲。我采用在pid上增加分段的限幅,比如在60°C以下在目标温度附近时最大加热占空比为40%,在60°C以上在目标温度附近时最大加热占空比为80%,这样就可以稳定在±2°C。否则不加限幅的话温度波动会达到±4°C。

                                /* TMR_CLK = F_CPU / PRESCALER = 24MHz / 4 = 6MHz */
#define PERIOD_EXAMPLE_VALUE        500       /* fpwm = 6000kHz / 2 / 500 = 6kHz */
#define DUTY_CYCLE_EXAMPLE_VALUE    (PERIOD_EXAMPLE_VALUE/2)   /* 50% duty cycle */
void TCA0_init(void)
{
    /* set waveform output on PORT C */
    PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTC_gc;
    
    TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm             /* enable compare channel 0 */
                      | TCA_SINGLE_WGMODE_DSBOTTOM_gc;   /* set dual-slope PWM mode */
    
    /* disable event counting */
    TCA0.SINGLE.EVCTRL &= ~TCA_SINGLE_CNTAEI_bm; 
    
    /* set PWM frequency and duty cycle (50%) */
    TCA0.SINGLE.PERBUF  = PERIOD_EXAMPLE_VALUE;       
    TCA0.SINGLE.CMP0BUF = DUTY_CYCLE_EXAMPLE_VALUE;  
    
    TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV4_gc        /* set clock source (sys_clk/4) */
                      | TCA_SINGLE_ENABLE_bm;            /* start timer */
}

void PORT_init(void)
{    
    /* set pin 0 of PORT C as output */    
    PORTC.DIRSET = PIN0_bm;
}
TCA0.SINGLE.CMP0BUF=PID_realize((float)(set_temp*16),(float)now_T);

   ↑pwm配置及应用

float PID_realize(float set_place,float now_place)
{
	pid.Set=set_place;
	pid.Actual = now_place;
	pid.err=pid.Set-pid.Actual;
	pid.integral+=pid.err;
	pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
	pid.err_last=pid.err;
    if(pid.integral>100)pid.integral=100;
    if(pid.integral<-100)pid.integral=-100;
    if(set_place-now_place>80){
        if(pid.voltage>500)pid.voltage=500;
    }
    else{
        if(set_place>960){
            if(pid.voltage>390)pid.voltage=390;//大于60°C
        }
        else{
	        if(pid.voltage>200)pid.voltage=200;
        }
    }
    if(pid.voltage<0)pid.voltage=0;
	
	pid.pwm=(int)pid.voltage;
	return pid.pwm;
}

   ↑pid核心部分

⑤屏幕的驱动

   屏幕由于需要较高的速度,所以我使用了硬件spi,硬件spi还是比较简单的。之后就把程序接口和屏幕的例程对接好,屏幕驱动就好了。

void SPI0_init(void)
{
    /* SPI in host mode setting */
    PORTA.PIN7CTRL |= PORT_PULLUPEN_bm; /* Pull-up enable on PIN7 */
    //PORTA.OUTSET = PIN7_bm;             /* Set SS pin to high state */
    PORTA.DIRSET = PIN4_bm;             /* Set MOSI pin direction to output */
    //PORTA.DIRCLR = PIN5_bm;             /* Set MISO pin direction to input */
    PORTA.DIRSET = PIN6_bm;             /* Set SCK pin direction to output */
   // PORTA.DIRSET = PIN7_bm;             /* Set SS pin direction to output */

    /* SPI Clock speed is set to 2 x 24MHz / 16 = 3 MHz */
    SPI0.CTRLA = SPI_CLK2X_bm           /* Enable double-speed */
               | SPI_ENABLE_bm          /* Enable module */
               | SPI_MASTER_bm          /* SPI module in Host mode */
               | SPI_PRESC_DIV16_gc;    /* System Clock divided by 16 */
}

uint8_t LCD_Writ_Bus(uint8_t sent_data)
{
    SPI0.DATA = sent_data;
    while (!(SPI0.INTFLAGS & SPI_IF_bm));  /* waits until data is exchanged */

    return SPI0.DATA;
}

   ↑spi配置部分以及和屏幕例程对接的接口部分,其余显示就是调用库,没什么好说的

⑥串口部分

   由于需要进行pid调参,所以使用串口发送温度传感器数据,使用arduino的串口绘图器进行绘制,就可以看到曲线图了。

FvXaZsz5fQJiS7OxOoI6L8SjWF_E

#define F_CPU 24000000UL
#define BAUD_RATE 115200
#define USART0_BAUD_RATE(BAUD_RATE) (uint16_t)((float)((F_CPU) * 64 / (16*6* (float)(BAUD_RATE))) + 0.5)


void USART0_Init(void)
{
    PORTMUX.USARTROUTEA = PORTMUX_USART0_ALT3_gc;   /* USART0 routed to PORTD that is connected to CDC */    
    PORTD.DIRSET = PIN4_bm;    

    USART0.BAUD  = USART0_BAUD_RATE(BAUD_RATE);     
    USART0.CTRLB |= USART_TXEN_bm;        
}

void USART0_sendChar(char c)
{
    while (!(USART0.STATUS & USART_DREIF_bm));
    USART0.TXDATAL = c;
}

void SendString(const char* pStr)
{     
    while(*pStr)
        USART0_sendChar(*pStr++); 
}

   ↑串口配置部分。

         USART0_sendChar(48+now_T/1000);
         USART0_sendChar(48+now_T/100%10);
         USART0_sendChar(48+now_T/10%10);
         USART0_sendChar(48+now_T%10);
         USART0_sendChar(',');
          USART0_sendChar(48+set_temp*16/1000);
         USART0_sendChar(48+set_temp*16/100%10);
         USART0_sendChar(48+set_temp*16/10%10);
         USART0_sendChar(48+set_temp*16%10);
         USART0_sendChar('\n');
         USART0_sendChar('\r');

   ↑串口发送数据,就按照arduino串口绘图器要求的数据格式进行发送。

三、心得体会

   通过funpack活动,可以玩一些新的单片机,锻炼了快速学习一款新单片机的能力。由于以前会stm32,stc,esp32等单片机,再加上这学期学校里单片机课程使用微芯pic,所以对mplab的开发有所熟悉,拿到这款avr64dd32配合官方的例程花了大概两三天时间就大致做出来了,确实锻炼了自己快速学习新器件并进行开发的能力。同时avr64dd32这款八位机也很好,有zcd过零检测,以及adc参考选负电源可以测负电压,这些功能是其他很多单片机所没有的,参加funpack的同时也获得了一款开发利器。

附件下载
avr64dd32-mplabx.zip
团队介绍
福州大学 电气工程及其自动化 郑凯文
团队成员
游泳的鸟儿
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号