LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Tania ATmega8 z Chin: podróbka czy okazja?

MilisecondsTimer.cpp

#include <avr/io.h>
#include <avr/interrupt.h>
#include "MillisecondTimer.h"
 
volatile uint32_t MillisecondTimer::_counter=0;
volatile uint8_t MillisecondTimer::_subCounter=0;
 
/*
 * Timer 0 interrupt handler
 */
 
ISR(TIMER0_OVF_vect) {
 
  MillisecondTimer::_subCounter++;
 
  if((MillisecondTimer::_subCounter & 0x3)==0)
    MillisecondTimer::_counter++;
 
  TCNT0+=6;
}

Teraz możemy odmierzać czas. Potrzebuję prostego sposobu na adresowanie pinów GPIO. Oto efektywne rozwiązanie: szablony i definicje typów pozwalają uzyskać czytelny kod, a implementacja w czystym asemblerze zapewnia, że rozwiązanie jest maksymalnie wydajne.

 

GpioPin.h

#pragma once
 
/*
 * GPIO ports
 */
 
struct GPIOB {
  enum {
    Port = 0x18,
    Dir  = 0x17,
    Pin  = 0x16
  };
};
 
struct GPIOC {
  enum {
    Port = 0x15,
    Dir  = 0x14,
    Pin  = 0x13
  };
};
 
struct GPIOD {
  enum {
    Port = 0x12,
    Dir  = 0x11,
    Pin  = 0x10
  };
};
 
/*
 * Base template for all GPIO pins. Provide support for set/reset
 */
 
template<uint8_t TPort,uint8_t TPin>
struct GpioPin {
 
  static void set() {
    asm volatile(
      "  sbi  %[port],%[pin]  \n\t"
      :: [port] "I" (TPort),
         [pin]  "I" (TPin)
    );
  }
 
  static void reset() {
    asm volatile(
      "  cbi  %[port],%[pin]  \n\t"
      :: [port] "I" (TPort),
         [pin]  "I" (TPin)
    );
  }
 
  static void changeTo(bool state) {
    if(state)
      set();
    else
      reset();
  }
};
 
/*
 * GPIO output, provide support for init
 */
 
template<class TPort,uint8_t TPin>
struct GpioOutputPin : GpioPin<TPort::Port,TPin> {
 
  static void setup() {    
    asm volatile(
      "  sbi  %[port],%[pin]  \n\t"
      :: [port] "I" (TPort::Dir),
         [pin]  "I" (TPin)
    );
  }
};
 
/*
 * GPIO input, provide support for init, read
 */
 
template<class TPort,uint8_t TPin>
struct GpioInputPin : GpioPin<TPort::Port,TPin> {
 
  static void setup() {    
    asm volatile(
      "  cbi  %[port],%[pin]  \n\t"
      :: [port] "I" (TPort::Dir),
         [pin]  "I" (TPin)
    );
  }
 
  static bool read() {
 
    uint8_t r;
 
    asm volatile(
        "  clr  %[result]       \n\t"       // result = 0
        "  sbic %[port],%[pin]  \n\t"       // skip next if port bit is clear
        "  inc  %[result]       \n\t"       // result = 1
      : [result] "=r" (r)
      : [port]   "I"  (TPort::Pin),
        [pin]    "I"  (TPin)
    );
 
    return r;
  }
};
 
 
/*
 * All pins used in this project
 */
 
typedef GpioOutputPin<GPIOD,5> GpioLed;

Po tych przygotowaniach napisanie funkcji main, która obsługuje miganie, jest trywialne.

 

Blink.cpp

#include <avr/io.h>
#include <avr/interrupt.h>
#include "GpioPin.h"
#include "MillisecondTimer.h"
 
int main() {
 
  MillisecondTimer::setup();
  GpioLed::setup();     // PD5 (pin 11)
 
  sei();
 
  for(;;) {
    GpioLed::set();
    MillisecondTimer::delay(1000);
 
    GpioLed::reset();
    MillisecondTimer::delay(1000);
  }
 
  // not reached
  return 0;
}

Kod jest już napisany i musimy go skompilować. Wykorzystuję narzędzie scons, ponieważ jest oparte na Pythonie – języku użytecznym też w innych sytuacjach.

Oto mój plik SConstruct. Wykorzystujemy program avrdude do wgrania pliku binarnego do pamięci mikrokontrolera za pomocą programatora USBAPA, a polecenie fuse ustawia zworki na wykorzystanie zewnętrznego oscylatora.

 

SConstruct

"""
Usage: scons [upload]
 
  [upload]
    specify this option to automatically upload to
    using avrdude to a USBASP connected MCU.
 
  To do a 'clean' use the -c flag
  To do a parallel build use the -jN flag
"""
 
import os
 
# source the environment
 
env=Environment(ENV=os.environ)
 
# compiler is avr-gcc
 
env.Replace(CC="avr-gcc")
env.Replace(CXX="avr-g++")
env.Replace(PROGSUFFIX=".elf")
 
# set up our options
 
env.Replace(CXXFLAGS=["-mmcu=atmega8",
                      "-Os",
                      "-g",
                      "-DF_CPU=8000000",
                      "-std=c++11",
                      "-Wall",
                      "-Werror",
                      "-Wextra",
                      "-pedantic-errors",
                      "-fno-rtti",
                      "-ffunction-sections",
                      "-fdata-sections",
                      "-fno-exceptions"])
 
env.Replace(LINKFLAGS=["-Wl,-Map,blink.map",
                       "-mrelax",
                       "-g",
                       "-Wl,--gc-sections",
                       "-mmcu=atmega8"])
 
# compile source code to .elf binary
 
elf=env.Program("blink",[Glob("*.cpp")])
Decider('timestamp-newer')
 
# convert elf to hex flashable image
 
env.Command("blink.hex",elf,"avr-objcopy -j .text -j .data -O ihex $SOURCE $TARGET")
 
# calculate size and generate assembly source
 
env.Command("blink.siz",elf,"avr-size $SOURCE | tee $TARGET")
env.Command("blink.lst",elf,"avr-objdump -S $SOURCE > $TARGET")
 
# upload target uses avrdude
 
flash_cmd="avrdude -c usbasp -p m8 -e -U flash:w:blink.hex"
 
# internal oscillator
#fuse_cmd="avrdude -c usbasp -p m8 -e -U lfuse:w:0xe4:m -U hfuse:w:0xd9:m"
 
# external crystal
fuse_cmd="avrdude -c usbasp -p m8 -e -U lfuse:w:0xff:m -U hfuse:w:0xd9:m"
 
upload=env.Alias("upload","blink.hex",flash_cmd)
fuse=env.Alias("fuse","blink.hex",fuse_cmd)
 
AlwaysBuild([upload,fuse])