Deek Robot BL-02 için alternatif firmware

taydin

Timur Aydın
Staff member
Katılım
24 Şubat 2018
Mesajlar
25,056
Elimde birkaç yıl önce aldığım Deek Robot BL-02 voltmetre/ampermetre paneli var. 0 - 100V ve 0 - 10A arası ölçüm yapabildiği ve hata oranının da %1 olduğu belirtiliyor. Ama benim yaptığım ölçümlerde hata oranının daha yüksek olduğunu gözlemledim. Üzerinde Nuvoton N76E003AT20 MCU var ve bu MCU ile ilgili bazı çalışmalar da yapmak istediğimden, bu panel için sıfırdan yeni firmware yazacağım. Bakalım nasıl bir ölçüm performansı elde edebileceğim göreceğiz.

Mevcut devre şeması şöyle

1732381776756.png
 
N76E003AT20 yi programlamak için Nuvoton'un kendi programlayıcılarının kullanılması gerekiyor. Bende NuLink Pro var. Windows için bedava programlayıcı yazılımını Nuvoton sağlıyor. Derleyici olarak Nuvoton Keil veya IAR'ı öne çıkarıyor, ama bunlar çok pahalı ve bedava versiyonlarının de kod üretme üst limiti var. Keil için bu 2 KByte. Bu ücretli IDE'ler yerine ben açık kaynak SDCC yi kullanıyorum.

 
Debug etmek için OpenOCD ile ilgili bazı çalışmalar yapılması gerekiyor, Nuvoton'un sağladığı özel versiyonu düzgün bir şekilde çalıştıramadım. O yüzden yazacağımız firmware'i bir debugger altında çalıştıramayacağız. Bu durumda da firmware'de neler olup bittiğini anlamak için ilk işimiz seri port üzerinden bir terminal oluşturmak olmalı.

Datasheet'i inceleyerek UART0 portunu uygun şekilde yapılandırdım. Baudrate olarak da şimdilik düşük bir hız seçtim (10000 baud). Sonra gerektiğinde arttırırız.

Kod:
#include "numicro_8051.h"

/***********************************************************************
 ***********************************************************************/
static void console_init(unsigned long baudrate)
{
    /* UART0 TX */
    P06_PUSHPULL_MODE;

    /* UART0 RX */
    P07_INPUT_MODE;

    /* UART0 mode 1, don't check stop bit, enable receive */
    SCON = 0x50;

    /* timer 1, mode 2 */
    TMOD = 0x20;

    /* UART0 double baud rate */
    set_PCON_SMOD;

    /* timer 1 is driven directly by the system clock */
    set_CKCON_T1M;

    /* UART0 clock source is timer 1 */
    clr_T3CON_BRCK;

    /* configure baud rate */
    TH1 = 256 - 1000000 / baudrate;

    /* enable timer 1 */
    set_TCON_TR1;

    /* initiate transmit operations */
    set_SCON_TI;
}

/***********************************************************************
 ***********************************************************************/
void main(void)
{
    console_init(10000);
}
 
Sonra da MCU kütüphanelerin içinde yer alan ve standart C kütüphanesindeki getchar ve putchar fonksiyonlarını UART haberleşmesi şeklinde gerçekleştiren kaynak kodunu projeye dahil ettim.

Kod:
/*---------------------------------------------------------------------------------------------------------*/
/*                                                                                                         */
/* SPDX-License-Identifier: Apache-2.0                                                                     */
/* Copyright(c) 2023 Nuvoton Technology Corp. All rights reserved.                                         */
/*                                                                                                         */
/*---------------------------------------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------------------------------------*/
/* Program Description:                                                                                    */
/*                                                                                                         */
/* This set of functions includes putchar (), getchar ()                                                   */
/* functionality for the SDCC compiler.                                                                    */
/*                                                                                                         */
/*---------------------------------------------------------------------------------------------------------*/
#include "numicro_8051.h"


//-----------------------------------------------------------------------------
// putchar
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : character to send to UART
//
// This function outputs a character to the UART.
//-----------------------------------------------------------------------------
#if 0
char putchar (char c)
{
    while (!TI_1);  /* wait until transmitter ready */
    TI_1 = 0;
    SBUF_1 = c;      /* output character */
    return (c);
}
#else
int putchar (int c)  {
  while (!TI);
  TI = 0;
  return (SBUF = c);
}
#endif

//-----------------------------------------------------------------------------
// getchar
//-----------------------------------------------------------------------------
//
// Return Value : character received from UART
// Parameters   : None
//
// This function returns a character from the UART.
//-----------------------------------------------------------------------------
int getchar (void)
{
   char c;

   while (!RI);
   c = SBUF;
   RI = 0;

   return c;
}

//-----------------------------------------------------------------------------
// End Of File
//-----------------------------------------------------------------------------
 
Bunu yapınca da artık linux'taki bir terminal emülatör ile MCU ya komut göndermek ve MCU nun da mesaj göndermesi mümkün hale geldi.

Kod:
#include "numicro_8051.h"

/***********************************************************************
 ***********************************************************************/
static void led_init(void)
{
    P15_PUSHPULL_MODE;

    P15 = 1;
}

/***********************************************************************
 ***********************************************************************/
static void console_init(unsigned long baudrate)
{
    /* UART0 TX */
    P06_PUSHPULL_MODE;

    /* UART0 RX */
    P07_INPUT_MODE;

    /* UART0 mode 1, don't check stop bit, enable receive */
    SCON = 0x50;

    /* timer 1, mode 2 */
    TMOD = 0x20;

    /* UART0 double baud rate */
    set_PCON_SMOD;

    /* timer 1 is driven directly by the system clock */
    set_CKCON_T1M;

    /* UART0 clock source is timer 1 */
    clr_T3CON_BRCK;

    /* configure baud rate */
    TH1 = 256 - 1000000 / baudrate;

    /* enable timer 1 */
    set_TCON_TR1;

    /* initiate transmit operations */
    set_SCON_TI;
}

/***********************************************************************
 ***********************************************************************/
void main(void)
{
    led_init();

    console_init(10000);

    while (1)
    {
        if (RI)
        {
            char c = SBUF;
            RI = 0;

            printf("c = 0x%02X\n", c);

            switch (c)
            {
                default:
                    break;
            }
        }
    }
}
 
Artık debug etme olanağımız olduğuna göre, firmware'in ne yapması gerektiğine odaklanabiliriz. Temelde bu firmware'in sürekli olarak voltaj ve akımı okuyup ekrana yazması lazım. Ama bunun içindeki ADC nin kalitesi ve lineerliği nedir bilmiyoruz. Muhtemelen çok iyi değildir. O yüzden özel bir kalibrasyon prosedürü ile lineerlik hatalarını dengeleme özelliği de eklemek istiyorum. Yani mesela bir GPIO pini şaseye çekilirse, firmware UART terminali kullanarak değişik voltajların verilmesini isteyecek, sonra da o voltaj için ne okuduğunu bir kenara kaydedecek. Sonra da bu tabloyu kullanarak interpolasyon yolu lile lineerlik hatalarını azaltacağız.

İlk önce ADC yi yapılandırdım ve giriş voltajı değişimlerinde ona göre değişen değerler okuduğumu teyit ettim. Bu kod, sürekli olarak ADC yi okuyor ve geçmişe dönük 40 tane değerin ortalamasını tutuyor. Sonra da bu ortalamayı peryodik olarak ekrana yazıyor.

Kod içinde bir de "bandgap reference" ile ilgili şeyler var. Bunlara daha sonra daha ayrıntılı değineceğim, ama bu kodların genel amacı şu: Bu kartta harici, yüksek kaliteli bir voltaj referansı yok. Referans, +5V Vcc oluyor. Bu değer de her ünitede değişebilecektir. Ayrıca ortam sıcaklığına göre ve yaşlanmaya göre de değişecektir. O yüzden bu bandgap reference kodunu kullanarak Vcc değerinin tam olarak belirliyoruz. Bu sayede de arka planda sürekli olarak Vcc referansındaki değişimleri hesaba katmış oluyoruz.

Kod:
#include "numicro_8051.h"

#define debug_log(format, args...) \
    printf("%s - " format, __func__, ## args)

static float adc_vdd;

/***********************************************************************
 ***********************************************************************/
static void led_init(void)
{
    P15_PUSHPULL_MODE;

    P15 = 1;
}

/***********************************************************************
 ***********************************************************************/
static void console_init(unsigned long baudrate)
{
    /* UART0 TX */
    P06_PUSHPULL_MODE;

    /* UART0 RX */
    P07_INPUT_MODE;

    /* UART0 mode 1, don't check stop bit, enable receive */
    SCON = 0x50;

    /* timer 1, mode 2 */
    TMOD = 0x20;

    /* UART0 double baud rate */
    set_PCON_SMOD;

    /* timer 1 is driven directly by the system clock */
    set_CKCON_T1M;

    /* UART0 clock source is timer 1 */
    clr_T3CON_BRCK;

    /* configure baud rate */
    TH1 = 256 - 1000000 / baudrate;

    /* enable timer 1 */
    set_TCON_TR1;

    /* initiate transmit operations */
    set_SCON_TI;
}

#define RAVG_BUFCNT 40

typedef struct
{
    unsigned int buffer[RAVG_BUFCNT];
    unsigned int index;
    unsigned long sum;

} RAVG;

static RAVG ravg_voltage;
static RAVG ravg_current;

#define SCALE_VOLTAGE 1.0
#define SCALE_CURRENT 1.0


/***********************************************************************
 ***********************************************************************/
static void ravg_run(RAVG* ravg, unsigned int data)
{
    ravg->sum -= ravg->buffer[ravg->index];
    ravg->sum += data;
    ravg->buffer[ravg->index] = data;
    ravg->index = (ravg->index + 1) % RAVG_BUFCNT;
}

/***********************************************************************
 ***********************************************************************/
static float ravg_average(RAVG* ravg)
{
    return (float)ravg->sum / RAVG_BUFCNT;
}

/***********************************************************************
 ***********************************************************************/
static unsigned int adc_read_data(void)
{
    clr_ADCCON0_ADCF;
    set_ADCCON0_ADCS;
    while ((ADCCON0 & SET_BIT7) == 0)
    {
        ;
    }

    unsigned int data = (ADCRH << 4) | (ADCRL & 0x0f);
    return data;
}

/***********************************************************************
 ***********************************************************************/
static unsigned int adc_bandgap_stored(void)
{
    unsigned int data;

    set_IAPEN;
    IAPAL = 0x0C;
    IAPAH = 0x00;
    IAPCN = 0x04;
    set_IAPGO;
    data = IAPFD;
    IAPAL = 0x0d;
    IAPAH = 0x00;
    IAPCN = 0x04;
    set_IAPGO;
    data = (data << 4) | (IAPFD & 0x0f);
    clr_IAPEN;

    debug_log("data = %u\n", data);
    return data;
}

/***********************************************************************
 ***********************************************************************/
static unsigned int adc_bandgap_actual(void)
{
    ENABLE_ADC_BANDGAP;

    adc_read_data();
    adc_read_data();
    adc_read_data();

    unsigned int data = adc_read_data();

    debug_log("data = %u\n", data);
    return data;
}

/***********************************************************************
 ***********************************************************************/
static void adc_read(RAVG* ravg)
{
    unsigned int data = adc_read_data();
    ravg_run(ravg, data);
}

/***********************************************************************
 ***********************************************************************/
static float adc_calc_value(RAVG* ravg, float scale)
{
    float value = ravg_average(ravg) * adc_vdd / 4095.0 * scale;
    return value;
}

/***********************************************************************
 ***********************************************************************/
static void adc_init(void)
{
    unsigned int bg_stored = adc_bandgap_stored();
    unsigned int bg_actual = adc_bandgap_actual();

    float vbg = 3072.0 * bg_stored / 4096.0;
    adc_vdd = 4095.0 / bg_actual * vbg;

    debug_log("bg_stored = %u, bg_actual = %u, vbg = %f mV, adc_vdd = %f mV\n",
              bg_stored, bg_actual, vbg, adc_vdd);
}

/***********************************************************************
 ***********************************************************************/
void main(void)
{
    led_init();

    console_init(10000);

    adc_init();

    unsigned long count = 0;
    while (1)
    {
        if (RI)
        {
            char c = SBUF;
            RI = 0;

            printf("c = 0x%02X\n", c);

            switch (c)
            {
                default:
                    break;
            }
        }

        ENABLE_ADC_AIN6;
        adc_read(&ravg_voltage);

        ENABLE_ADC_AIN5;
        adc_read(&ravg_current);

        ++count;
        if (count == 1000)
        {
            count = 0;
            float value = adc_calc_value(&ravg_voltage, SCALE_VOLTAGE);
            debug_log("voltage = %f mV\n", value);
        }
    }
}
 
Şu anda girişe tam olarak 5.000V veriyorum ve terminalde de kararlı bir değer okuyorum. Bandgap reference kodu sayesinde Vcc yi de 5.198V olarak ölçüyoruz. Aslında bu tam doğru bir değer değil. Gerçek Vcc 5.034V. Ama önemli de değil. Neticede bizim değişimi algılamamız ve ona göre ölçekleme katsayısını ayarlamamız gerekir.

1732227714157.png
 
MCU içerisinde 18 KByte bellek var, mevcut printf desteği ve floating point aritmetik desteği ile 10.2 KByte yer kullanmış oldum. Geri kalan yer rahatlıkla yeter, hatta kalibrasyon katsayılarını saklamak için de bir yer ayırabileceğim.

Keil de beklesin dursun ben 3500 dolar vereyim de 2 KByte kod limitini aşayım diye :temizlik1:
 
Float derinliğini siz belirleyin ve bunu integer olarak dönüştürün. Alttaki linkteki itoa ile tam değeri ile birlikte dönüştürün. Epeyce yer kazanılıyor.

 
Yer kazanmaya ihtiyacım olmayacak. İlgili kütüphanelerin hepsi yüklü şu anda ve yeterince yerim var.
 
Son günlerde biraz daha çalışma fırsatım oldu firmware üzerinde. Voltaj ve akım ölçüm işlerini tamamladım. Kalibrasyonu da otomatik olarak yapıp flash'a kaydedecektim ama onu yapmaya üşendim. Manuel olarak kalibre edip, katsayıları doğrudan kodun içine yazdım. Şu anda %1 den çok daha iyi doğrulukla ölçüm yapabiliyorum. Yalnız şöyle bir sıkıntı var: N76E003AT20 nin ADC sinde lineerlik hatası gerçekten vahim. 5V dan 0.3 V a kadar voltajı azaltıyorum, ADC den de azalan bir voltaj okuyorum. Ama 0.3V un altına inersem, ADC den okunan değer artmaya başlıyor! O yüzden mecbur 0.3V aşağısını yok saymak gerekiyor, anlamlı bir ölçüm olmuyor. Akım ölçümünde de minimum 10 mA e kadar ölçüm yapılabiliyor, yani ekranın desteklediği tüm aralığı ölçebiliyoruz.

1732915747949.jpeg


1732915768341.jpeg
 
Firmware'i github'a yükledim. 12.1 KByte yer tutuyor kod. Lineerlik hatasını düzeltmek için de lineer interpolasyon tekniğini kullanıyor.

 
İşlemci aynı ise yükleyebilirsin. Programlama adaptörü de alman gerekir, mesela nulink veya nulink pro. Bir de devre şemasını da çıkarmak gerekir. Bu çinliler çok çakal, SWD adaptörün bağlanacağı pinleri standart sırayla dizmemişler, "bul kareyi al parayı" usulü sırayı karıştırmışlar :D
 
Şimdi bendekine baktım fiziksel olarak tamamen farklı. Belki farklı bir kılıftır. Üzerindeki yazılar da büyüteçle okunamıyor.

1732993923180.png
 
Lazerle kazımışlar işlemcinin üstünü zaten.

Ama devre yeterince basit. Bileşenleri satın alıp sıfırdan kendin yapabilirsin.

Benim bu karta firmware yazmamın ana sebebi üzerindeki Nuvoton işlemci. Bu hem çok ucuz hem de birçok çevre birimi olan fiyat/performans canavarı bir MCU. Deek robotta işlemciyi ilk defa kullanmış oldum.

Korad 3005 de de aynı işlemci var, JCD8898 de de aynı işlemci var. Onlara da alternatif firmware yazma şansımız var artık :)
 
Madem önemli bir işlemci, Sepete eklemiştim alayım bari :D
1732994988424.png



UT210E pensampermetre gelince onuda hackleyeceğim, onun için de şunları aldım.
1732995127825.png
 

Forum istatistikleri

Konular
7,554
Mesajlar
126,503
Üyeler
3,057
Son üye
Hacın

Son kaynaklar

Son profil mesajları

Python Geliştirmeye eklediğim yapay zeka sunucusu, yeni başlayanlar için roket etkisi
Bir insanın zeka seviyesinin en kolay tesbiti, sorduğu sorulardır.
yapay zeka interneti yedi bitirdi, arama motoru kullanan, forumlara yazan kaldı mı ?
Freemont2.0 herbokolog Freemont2.0 wrote on herbokolog's profile.
nick iniz yakıyor
:D
az bilgili çok meraklı
Back
Top