1.26.2014

UART - Realizacja komunikacji 1-Wire z termometrem DS18B20 - EK-TM4C123GXL


Mikro-kontroler TM4C123GH6PM posiada 8 modułów UART - Universal Asynchronous Receiver/Transmitter. Do obsługi transmisji 1-Wire należny przygotować wyjście TX i wejście RX tak aby sygnały były współdzielone z jednym przewodem na którym musi być oparta cała transmisja. Do fizycznego połączenia pinów RX i TX potrzebne są dwa tranzystory NPN BC547 i dwa rezystory, przy testach i pisaniu kodu korzystałem z dokumentacji Atmel  i Maxim integrated.


Testowa płytka stykowa połączona z lanunchpadem.



Do inicjacji UART służy poniższa funkcja, nie wykorzystałem przerwań i buforów FIFO. Należy  w opcjach projektu  dodać "predefined symbols" "PART_LM4F230H5QR", gdyż bez tego biblioteka nie zwróci odpowiednich map dla naszego procesora. W zależności od płytki może być potrzebny inny zapis, więcej na forum TI link.

 
void OneWire_init(void)
{
  // Enable Peripheral Clocks
 SysCtlPeripheralEnable(SYSCTL_PERIPH_UART1);
 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
 // Enable pin  for UART URX
 GPIOPinConfigure(GPIO_PB0_U1RX);
 GPIOPinTypeUART(GPIO_PORTB_BASE, GPIO_PIN_0);
 // Enable pin  for UART UTX
 GPIOPinConfigure(GPIO_PB1_U1TX);
 GPIOPinTypeUART(GPIO_PORTB_BASE, GPIO_PIN_1);

 UARTClockSourceSet(UART1_BASE, UART_CLOCK_SYSTEM);
 UARTConfigSetExpClk(UART1_BASE, SysCtlClockGet(), 115200, (UART_CONFIG_WLEN_8|UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
 UARTFIFODisable(UART1_BASE);
 UARTEnable(UART1_BASE);
}

Podstawowe funkcje jakie będą niezbędne do obsługi 1-Wire na wyższym poziomie to wczytywanie 1, wczytywanie 0, odczyt jednego bitu i reset.  W jednym bajcie przekazywany jest tylko jeden bit, cała ramka jest potrzebna do spełnienia czasowego wymogu transmisji. Baud rate ustawione w większości operacji na 115200.
Po nadaniu bajtu funkcja czeka aż bajt zostanie wysłany przez UART i następnie odebrany i odczytany (możliwe, że odczytanie jest zbędne, ma na celu oczyszczenie bufora).

Wczytanie 0 na magistrale, TX nadaje bajt składający się z samych zer.

void OneWire_write0()
{
 UARTCharPut(U_BASE, 0x00);
 while(UARTBusy(U_BASE))
 {
 }
 while(UARTCharsAvail(U_BASE))
 {
  UARTCharGetNonBlocking(U_BASE);
 }
}


Wczytanie 1 na magistrale, TX nadaje bajt składający się z samych 1.

void OneWire_write1()
{
 UARTCharPut(U_BASE, 0xFF);
 while(UARTBusy(U_BASE))
 {
 }
 while(UARTCharsAvail(U_BASE))
 {
  UARTCharGetNonBlocking(U_BASE);
 }
}

Odczytanie jednego bitu ze slave. Wczytanie 1 na magistrale i odczyt bajtu który przy zmodyfikowaniu przez slave będzie inny niż 1 co oznacza nadanie przez slave 0.

Read 0

Read 1

unsigned char OneWire_Read()
{
 // function reads 1-wire bus
 // return 1 or 0

 unsigned long tmp;
 UARTCharPut(U_BASE, 0xFF);
 while(UARTBusy(U_BASE))
 {
 }
 while(UARTCharsAvail(U_BASE))
 {
  tmp=UARTCharGetNonBlocking(U_BASE);
 }
 return (0xFF==tmp);
}

Dodatkową ważną funkcja jest reset, musi być wykonany przy niższym baud rate = 9600.
Polega na wczytanie 0xF0 na magistrale i odczycie bajtu który przy zmodyfikowaniu przez slave będzie inny niż 0xF0 co oznacza, że jest działający slave na magistrali.

Reset - brak slave na magistrali.


Reset - slave odpowiedział.



unsigned char OneWire_Reset(void){

 // function reset bus and returns 1 if is presence pulse

 unsigned long tmp=0;
 UARTDisable(U_BASE);
    UARTConfigSetExpClk(U_BASE, SysCtlClockGet(), 9600, (UART_CONFIG_WLEN_8|UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
    UARTEnable(U_BASE);

 UARTCharPut(U_BASE, 0xF0);
 while(UARTBusy(U_BASE))
 {
 }
 while(UARTCharsAvail(U_BASE))
 {
  tmp=UARTCharGetNonBlocking(U_BASE);
 }

 UARTDisable(U_BASE);
    UARTConfigSetExpClk(U_BASE, SysCtlClockGet(), 115200, (UART_CONFIG_WLEN_8|UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));
    UARTEnable(U_BASE);
 return (0xF0!=tmp);
}

1.11.2014

PWM sprzętowy - EK-TM4C123GXL

Mikro-kontroler TM4C123GH6PM posiada dwa moduły PWM, każdy moduł posiada cztery generatory, każdy generator generuje dwa sygnały oparte na wspólnym zegarze. W sumie jest 16 wyjść PWM. Generatory mogą być synchronizowane.

Prosty kod włączajacy sygnał PWM na piny PC4 i PC5. Należy  w opcjach projektu  dodać "predefined symbols" "PART_LM4F230H5QR", gdyż bez tego biblioteka nie zwróci odpowiednich map dla naszego procesora. W zależności od płytki może być potrzebny inny zapis, więcej na forum TI link

 
#include "utils/ustdlib.h"
#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/pin_map.h"
#include "driverlib/debug.h"
#include "driverlib/hibernate.h"
#include "driverlib/gpio.h"
#include "driverlib/systick.h"
#include "driverlib/timer.h"
#include "driverlib/pwm.h"

int main(void)
{
 SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_OSC_INT);  // 40 MHz int
 SysCtlPWMClockSet(SYSCTL_PWMDIV_64); //PWM - pre-divided System Clock 625 kHz

 SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM0);  // PWM0

 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC); // PWM PC4
        // Enable pin PC5 for PWM0 M0PWM7
        GPIOPinConfigure(GPIO_PC4_M0PWM6);
        GPIOPinTypePWM(GPIO_PORTC_BASE, GPIO_PIN_5);
        // Enable pin PC4 for PWM0 M0PWM6
        GPIOPinConfigure(GPIO_PC5_M0PWM7);
        GPIOPinTypePWM(GPIO_PORTC_BASE, GPIO_PIN_4);

 PWMGenConfigure(PWM0_BASE, PWM_GEN_3, PWM_GEN_MODE_DOWN| PWM_GEN_MODE_NO_SYNC); // PWM Count-Down Mode

 PWMGenPeriodSet(PWM0_BASE, PWM_GEN_3, 6249); // 100 Hz

 PWMPulseWidthSet(PWM0_BASE, PWM_OUT_6, 3124); // PC4 50%

 PWMPulseWidthSet(PWM0_BASE, PWM_OUT_7, 1562); // PC5 25%

 PWMGenEnable(PWM0_BASE, PWM_GEN_3);

 PWMOutputState(PWM0_BASE, (PWM_OUT_6_BIT | PWM_OUT_7_BIT), true);

 while(1)
 {
 }
}
Sygnał generowany na pinach PC4 i PC5.


Można ustawić odwrócenie sygnału na wyjściu.
Funkcja PWMOutputInvert(PWM0_BASE, PWM_OUT_7_BIT, true)

Invert


Można ustawić licznik PWM w opcji up-down. Przebieg na dwóch wyjściach generatora będzie uzależniony od zadziałania COMP podczas liczenia w górę i w dół, nie są brane  wartości licznika 0 i load. Obraz dwóch sygnałów można podzielić na symetryczne części.
Funkcja PWMGenConfigure(PWM0_BASE, PWM_GEN_3, PWM_GEN_MODE_UP_DOWN | PWM_GEN_MODE_NO_SYNC)

up-down timer


Można również ustawić tryb pracy "dead-band generator", jest wtedy brany tylko czas ustawiony dla COMPA  (wyjście 0 danego generatora)  i na jego podstawie generowane są dwa sygnały na wyjściach generatora. Do ustawienia jest czas martwy Rising Edge i Falling Edge.
Funkcja PWMDeadBandEnable(PWM0_BASE, PWM_GEN_3,300,100);

Dead-Band Generator

1.10.2014

Konfiguracja Watchdog EK-TM4C123GXL


Mikro-kontroler TM4C123GH6PM posiada dwa moduły watchdog, WDT0 i WDT1. Różnica miedzy modułami polega na korzystaniu z różnych zegarów, Watchdog Timer 0 korzysta z systemowego zegara a Watchdog Timer 1 z PIOS. Timer WDT1 posiada dodatkowy bit zdefiniowany w rejestrze Watchdog Timer Control (WDTCTL) który po zapisie jest zerowany, kolejna operacja na rejestrze może być wykonana po pojawieniu się jedynki w tym bicie. Dodatkowo z modułem  WDT1 związanych jest kilka błędów w samym krzemie i aby go uruchomić należy zastosować się do erraty wydanej przez producenta. Testowałem WDT1 i bez zapoznania się z erratą nie udało się zainicjować jego działania w standardowy sposób. Po dokonaniu zalecanych przez producenta operacji zadziałał.

Watchdog posiada:

  • 32-bitowy licznik z programowalna wartością startową
  • możliwość generowania przerwania, opcjonalnie nie maskowanego NMI
  • możliwość generowania resetu
  • możliwość zablokowania zmian w konfiguracji w trybie działania programu
  • możliwość ustawienia stopu timera podczas kroków debugowania




Aby uruchomić sprzętowy WDT należy w rejestrze Watchdog Timer Run Mode Clock Gating Control (RCGCWD) ustawić bit R0 lub R1. Funkcja SysCtlPeripheralEnable(moduł).

Jeżeli WDT ma zliczać od wartości innej niż pełne słowo 32bit należy wypełnić rejestr WDTLOAD wartością startową. Funkcja WatchdogReloadSet(wartość).

Jeżeli ma być generowany reset, należy ustawić bit RESEN  w rejestrze WDTCTL. Funkcja WatchdogResetEnable().

Można ustawić rodzaj przerwania, bit INTTYPE w rejestrze WDTCTL. 0=normal,1=NMI. Funkcja WatchdogIntTypeSet().

Należy włączyć WDT ustawiając bit INTEN w rejestrze WDTCTL. Przerwanie będzie włączone i WDT zacznie zliczanie. Bit INTEN można programowo ustawić tylko raz, potem zmiany będą ignorowane. Aby wyłączyć WDT należy wykonać reset sprzętowy lub zresetować programowo przez Watchdog Timer Software Reset (SRWD).

Można dodatkowo zablokować możliwość zmian w rejestrach należy wpisać 0x1ACC.E551 do rejestru  Watchdog Lock (WDTLOCK). Odczyt z rejestru zwraca 0 dla nie zablokowanego i 1 dla zablokowanego. Funkcja WatchdogLock().


Prosty kod z wykorzystaniem WDT0 bez generowania resetu, z włączoną obsługą przerwania. Funkcja WatchdogIntRegister() przypisuje wektor do przekazanej funkcji i automatycznie odblokowuje przerwanie globalne.


 

#include "utils/ustdlib.h"
#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/pin_map.h"
#include "driverlib/debug.h"
#include "driverlib/hibernate.h"
#include "driverlib/gpio.h"
#include "driverlib/systick.h"
#include "driverlib/timer.h"
#include "driverlib/watchdog.h"

void WDT_interrupt(void); // WDT handler

int main(void)
{
 SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_OSC_INT);  // 40 MHz int

 //green LED on
 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
 GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
 GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 0x08);

 SysCtlPeripheralEnable(SYSCTL_PERIPH_WDOG0);

 WatchdogReloadSet(WATCHDOG0_BASE,0xFEEFEE);

 WatchdogStallEnable(WATCHDOG0_BASE); // debbuger

 WatchdogEnable(WATCHDOG0_BASE);

 WatchdogIntRegister(WATCHDOG0_BASE, WDT_interrupt ); // interrupt handler

 while(1)
 {
 }
}


void WDT_interrupt(){

 WatchdogIntClear(WATCHDOG0_BASE); // clear int
 static unsigned long fl; // flag blink led
 fl^=1; // toggle flag

 if(fl){
  GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 0x08);
 }
 else{
  GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 0x00);
 }
}









1.05.2014

Mikro projekt timera ze stałym czasem 60 sekund i automatycznym restartem. Mikro-kontroler ATtiny13A zasilany baterią CR2032.


Timer służy do ciągłej pracy w pętli, odlicza 60 sekund i generuje dźwięk, następnie rozpoczyna odliczanie od nowa (funkcja wykorzystywana do odliczania czasu na ruch w grze). W czasie odliczania dodatkowo generowane są błyski co 1 sekundę- 20 błysków zielonych, następnie 20 czerwonych i przez ostatnie 20 sekund błyski niebieskie. Wciśnięcie przycisku START resetuje timer i rozpoczyna zliczanie od nowa (60s). Timer nie posiada przycisku odłączającego zasilanie, po uruchomieniu lub resecie wchodzi w tryb power-down i teoretycznie na baterii CR2032 (około 200mAh / <1uA) powinien spać przez 20 lat ( bateria tyle nie wytrzyma). Program ogranicza działanie timera do 60 minut. Jeżeli nie zostanie w tym czasie wciśnięty przycisk START program uzna, że timer nie jest już używany i procesor przejdzie w uśpienie power down aby nie wyczerpać baterii. Każde wciśnięcie przycisku START resetuje zliczanie i kolejne wyłączenie nastąpi po 60 minutach.

Zatrzymanie timera polega na fizycznym resecie procesora przyciskiem STOP, po ponownym uruchomieniu procesor automatycznie przejdzie w tryb power down z wyłączonym "watchdog" - prąd pobierany z baterii <1uA.

Rezystor ograniczający prąd diod został dobrany eksperymentalnie, dla 3V diody- zielona i czerwona obciążają baterię prądem 5 mA a niebieska około 1,5 mA. Ponieważ czas ich działania to 0,005 sekundy w ciągu jednej sekundy, średni pobór prądu wynosi 25 uA podczas działania timera. Buzer który został zastosowany teoretycznie powinien działać przy napięciu 5V, przy zasilaniu 3V przepływa przez niego 10 mA prądu. Czas jego działania to 150 ms na 60000 ms, średnio obciąża baterie prądem 25uA podczas działania timera. Procesor podczas działania timera jest budzony na około 5 ms, jego pobór prądu wynosi wtedy 300 uA (mierzone dla częstotliwości 600 kHz)- średnio daje to  1,5 uA. Przez 995 ms procesor śpi w trybie power-down z włączonym "watchdog"- mierzone obciążenie około 6 uA.

Sumarycznie średnie obciążenie baterii podczas działania timera wynosi około 60-70 uA, teoretycznie timer może pracować ciągle przez ponad 100 dni zasilany jedną baterią CR2032.





Schemat.


Całość została wykonana z elementów które aktualnie posiadałem, lutowanie na płytce uniwersalnej. Można wykorzystać jedną diodę RGB co zmniejszy ilość elementów.




Programowanie z wykorzystaniem standardowej podstawki.



Poniżej kod programu napisany w C  w środowisku Eclipse.


 
/*
 * main.c
 *
 *  Created on: 2014-01-05
 *  Autor: Tomasz Pliszczak
 */
/*
 *
 * Fuses  = 69 FF
 * Frequency 600 000 Hz
 *
 */
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

// def

#define BUTTON (1<<PB1)
#define LED_1 (1<<PB0)
#define LED_2 (1<<PB2)
#define LED_3 (1<<PB3)
#define BUZZER (1<<PB4)

volatile uint16_t minute, for_long, time_play;

void WDT_on(void);
void WDT_off(void);

int main(void){

    _delay_ms(500);

    // low power
    //turn off Analog Comparator
    ACSR |= (1<<ACD);
    //turn off ADC
    ADCSRA &= ~(1<<ADEN);

    // Use the Power Down sleep mode
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);

    // int0
    GIMSK  |= (1<<INT0);
    GIFR |= (1<<INTF0);
    DDRB &= ~(BUTTON);// IN
    PORTB|=BUTTON; //VCC

    DDRB |= LED_1 |  LED_2 | LED_3 | BUZZER; // out
    PORTB&=~( LED_1 |  LED_2 | LED_3 | BUZZER);  // all off

    // on start - 3x buzzer 100 ms
    for (int8_t i = 0; i < 3; ++i) {
     PORTB|=BUZZER;
     _delay_ms(100);
     PORTB&=~BUZZER;
     _delay_ms(200);
    }

    // Turn off WDT
    WDT_off();

    time_play=60; // one countdown =60s

    sei();

while(1){

    sleep_mode(); // power down <1uA

    }

}

//functions

void WDT_on(void) {

 wdt_reset();
 /* Clear WDRF in MCUSR */
 MCUSR &= ~(1<<WDRF);
 WDTCR |= (1<<WDCE) | (1<<WDE);
 WDTCR |= (1 << WDP1) | (1 << WDP2) | (1 << WDTIE ) | (1<<WDE); // timer goes off every 1 seconds and interrupt

 wdt_reset();
 /* Clear WDRF in MCUSR */
 MCUSR &= ~(1<<WDRF);
 WDTCR |= (1<<WDCE) | (1<<WDE);
 WDTCR &= ~(1 << WDE ) ; // reset off
}

void WDT_off(void){

 cli();
 wdt_reset();
 /* Clear WDRF in MCUSR */
 MCUSR &= ~(1<<WDRF);
 WDTCR |= (1<<WDCE) | (1<<WDE);
 /* Turn off WDT */
 WDTCR = 0x00;
 sei();
}

// interrupts
// INT0 after pushing the button
ISR(INT0_vect) {
 minute=time_play; // play time
 for_long=3600;  // stop counting after 3600 = 1h and go sleep
 WDT_on(); //WDT ON
}

// WDT
ISR(WDT_vect) {

 //counter decrease
 minute--;
 for_long--;

 // blinking led (5ms) - 20x green, red, blue
 if( minute>(time_play-(time_play/3)-1)){
  PORTB|=LED_1;
  _delay_ms(5);
  PORTB&=~LED_1;
 }
 else if (minute>((time_play/3)-1)) {
  PORTB|=LED_2;
  _delay_ms(5);
  PORTB&=~LED_2;
 }
 else {
  PORTB|=LED_3;
  _delay_ms(5);
  PORTB&=~LED_3;
 }

 // after one minute start again
 if(minute<1){
  minute=time_play;
  // buzzer on
  PORTB|=BUZZER;
  _delay_ms(150);
  PORTB&=~BUZZER;
 }

 // after one hour - go to sleep
 if(for_long<1){
  WDT_off();
 }
}