вторник, 29 ноября 2011 г.

Быстрый старт с С на PIC используя SDCC и Linux

Уважаемые читатели, хотел бы поделиться с вами одной интересной статьей. Опубликована она на английском языке, но часто многие энтузиасты просто не знают столь хорошо английский язык. Я постараюсь объяснить все так, чтобы было просто и понятно.
Данное статья посвящена исключительно программированию. При этом она не является подробным описанием синтаксиса C.
Скажу честно, при программировании на данном языке надо его знать. Если вы до этого программировали исключительно для систем (т.е. для компьютера). То лично мой совет, вы всегда должны знать, в какой системе счисления вы работаете!
Сегодня я зашел в гости к другу на работу, у него там машинка игрушечная на AVR, решил позабавиться написать на нее программу. Я потратил 3 часа, на то чтобы понять где мои изначальные ошибки (но это другая история).
Начнем.
Во-первых, SDCC работает в связке с gputils (это пакет для программирования на языке assembler'a). Изначально у нас есть исходный файл, с помощью SDCC мы получаем файл на языке assembler'a. Потом происходит этап создания объектного файла с помощью gpasm (его вы найдете в пакете gputils). Дальше с помощью линковщика получаем hex файл. Это вы можете увидеть на рисунке:




После чего hex файл зашиваем в МК.
Как установить SDCC можно найти в интернете, если вы пользуетесь Debian и ее производными (Ubuntu, Mint и т.д.) то задача облегчается.
sudo apt-get install sdcc
Для начала воспользуемся простым примером, готовой программы все с того же сайта, но скажу сразу, что я его буду изменять. Вот текст программы:

// blinkled.c
//
// simple minimal program that blinks a LED

// ------------------------------------------------
// configuration
// change these if you're wiring the LED to a pin other than RB1

#define LED_TRIS  TRISBbits.TRISB1
#define LED_PIN   PORTBbits.RB1


// ------------------------------------------------
// this #include pulls in the correct processor-specific registers
// definition file

#include "pic18fregs.h"


#pragma stack 0x200 64

code char at __CONFIG1H conf1 = 0x22; // Select HS OSC
code char at __CONFIG4L conf2 = 0x81; // Disable LVP
code char at __CONFIG2H conf3 = 0x0E; // DIsable WDT

// ------------------------------------------------
// a simple delay function

void delay_ms(long ms)
{
    long i;

    while (ms--)
        for (i=0; i < 330; i++)
            ;
}

// --------------------------------------------------
// and our main entry point

void main()
{

    // set pin to output
    LED_TRIS = 0;

    // sit in an endless loop blinking the led
    for (;;)
    {
        LED_PIN = 0;
        delay_ms(250);
        LED_PIN = 1;
        delay_ms(250);
    }
}
Теперь я поясню, что я в первую очередь изменю. Во-первых, зачем самому писать функцию delay_ms(long) если уже существует такая в папке pic16 файл называется delay.h. Для того, чтобы создать задержку, есть несколько функций:

void delay10tcy(unsigned char) __wparam;
void delay100tcy(unsigned char) __wparam;
void delay1ktcy(unsigned char) __wparam;
void delay10ktcy(unsigned char) __wparam;
void delay100ktcy(unsigned char) __wparam;
void delay1mtcy(unsigned char) __wparam;
Теперь поясню tcy это цикл микроконтроллера (как говорит великий и ужасный Никулин Владислав Генадьевич: "НЕ ПУТАТЬ ЦИКЛ С ТАКТОМ!!!").
Что меня порадовало, хотя бы программисты SDCC понимают, что функция не может четко ms задержку сделать, ибо это зависит от частоты тактового генератора, что МК не известно.
10     10*n, циклов, где n параметр функции.
100   100*n циклов задержки
1k     1000*n циклов задержки
10k   10000*n циклов задержки
100k 100000*n циклов задержки
1m    1000000*n циклов задержки.
Теперь программа выглядит так:

// helloled.c
//
// Простой пример, своего рода "Hello, World!!!"
// только на МК




// ------------------------------------------------
// Конфигурация
// здесь используем сам МК как ноль и единицу
// хотя могли бы использовать землю.


#ifndef LED_TRIS
#define LED_TRIS  TRISAbits.TRISA1
#endif


#ifndef LED_PIN
#define LED_PIN   PORTAbits.RA1
#endif


// ------------------------------------------------
// Данные директивы #include включают необходимые заголовочные файлы


#include "pic18fregs.h" // Здесь хранятся адреса всех битов МК
#include "delay.h" // Здесь хранятся прототипы функций задержки


// --------------------------------------------------
// и вот наша главная функция, для С/С++ является обязательной


void main()
{
    // Устанавливаем бит на выход
    LED_TRIS = 0;
    LED_PIN = 1;
    // Цикл в котором постоянно инвертируем LED_PIN
    while(1)
    {
        LED_PIN = LED_PIN ^ 0x01;
        delay1ktcy(250);
    }
}

Скомпилировать ее можно командой: sdcc -mpic16 -p18f2550 helloled.c
Как вы заметили МК я тоже поменял. Не люблю полностью повторять урок, охота его изменить, по возможности улучшить. Тем более согласитесь, моя программа меньше, к сожалению работоспособность оной, я неизвестно когда проверю, но надеюсь скоро (если по учебе не грузанут, то я бы сказал, что очень скоро).
Теперь рассмотрим второй пример программы все с того же сайта.

/* Morse output 
   sdcc demo program
   compile with  "sdcc -mpic16 -p18f452 morse.c"
   (c) P.J.Onion 2005
*/

#include "pic18fregs.h"

include the definitions needed to use the interrupt handler/signal techniques
#include "signal.h"

define the location and size of the runtime stack
#pragma stack 0x200 0x40

set the configuration bytes (Set HS xtal oscillator, disable LVP, disable WDT) 
code char at __CONFIG1H conf1 = 0x22;
code char at __CONFIG4L conf2 = 0x81;
code char at __CONFIG2H conf3 = 0x0E;

symbolic definitions of numbers to control length of dashes and dots
#define DASH 250
#define DOT 100

symbolic definitions for the pin used to drive the LED
#define LED_TRIS  TRISBbits.TRISB7
#define LED_PIN   PORTBbits.RB7

An initialised array of bytes containing the dot/dash codes for each letter
unsigned char codes[3] = {
/* A */ 0x60,
/* B */ 0x88,
/* C */ 0xA8
};

unsigned char intCounter = 0;
unsigned char timer;

Definitions for using Timer0 interrupts
DEF_INTHIGH(high_int)
DEF_HANDLER(SIG_TMR0, _tmr0_handler)
END_DEF 

This is the interrupt handler called when timer0 overflows
SIGHANDLER(_tmr0_handler)
{

  /* action to be taken when timer 0 overflows */
  /* decrement intCounter until it reaches zero */
    if(intCounter) intCounter -= 1;
    INTCONbits.T0IF = 0;   /* Reset the Timer0 interrupt pending flag */
} 

/* Pause while intCounter is counting down to zero */
void delay(unsigned char time)
{
    intCounter = time;
    while(intCounter) ;
}

void sendLetter(unsigned char index)
{
    unsigned char shift;
    shift = codes[index];
    do
    {
        LED_PIN = 1;
        if(shift & 0x80)
        {
            delay(DASH);
        }
        else
        {
            delay(DOT);
        }
        LED_PIN = 0;
        delay(DOT);
        shift <<= 1;
    } while(shift != 0x80);
    delay(DOT);
}

void main(void)
{
    /* Set LED pin as an output */
    LED_TRIS = 0;

    /*Enable high and low priority interrupts */
    RCON = 0;
    RCONbits.IPEN = 1;

    /* Configure timer0 */
    T0CONbits.T0PS0 = 0;
    T0CONbits.T0PS1 = 0;
    T0CONbits.T0PS2 = 1;

    T0CONbits.PSA = 0;
    
    T0CONbits.T0SE = 0;
    T0CONbits.T0CS = 0;
    T0CONbits.T08BIT = 1;
    T0CONbits.TMR0ON = 1;

    /* enable timer0 interrupt as a high priority source */ 
    INTCON2bits.T0IP = 1;
    INTCONbits.T0IE = 1;
    
    /* Enable interrupts */
    INTCONbits.GIE = 1;

    while(1)
    {
      sendLetter(0);
      sendLetter(1);
      sendLetter(2);
    }
}

Для начала я опять избавился от ненужной функции задержки, данная программа, оказалась, непереносима, как предыдущая на PIC18F2550. Причина разумеется в регистрах! Но об этом сразу после выкладки кода с русскими комментариями.

#include "pic18fregs.h"
#include "delay.h"


//Включаем заголовочный файл необходимый для обработки сигналов прерываний
#include "signal.h"


//Определяем адрес и размер стэка
#pragma stack 0x200 0x40


//Устанавливаем конфигурационные биты (Высокочастотный кварц, 
//отключено низковольтное программирование и сторожевой таймер 
code char at __CONFIG1H conf1 = 0x22;
code char at __CONFIG4L conf2 = 0x81;
code char at __CONFIG2H conf3 = 0x0E;


//Символьное определение значения длины
#define DASH 250 //Тире
#define DOT 100 //Точка


//Символьное определение порта, к которому подключен LED
#define LED_TRIS  TRISBbits.TRISB7
#define LED_PIN   PORTBbits.RB7


//Инициализируем массив байтов, содержащих коды Морзе, для каждой буквы
unsigned char codes[3] = {
/* A */ 0x60,
/* B */ 0x88,
/* C */ 0xA8
};


unsigned char intCounter = 0;
unsigned char timer;


//Определение необходимое для использования прерываний от таймера 0
DEF_INTHIGH(high_int)
DEF_HANDLER(SIG_TMR0, _tmr0_handler)
END_DEF 


//Это обработчик прерывания, который вызывается, когда таймер переполняется
SIGHANDLER(_tmr0_handler)
{
  /* Действие, которое выполняется когда Таймер 0 переполнен */
  /* Декрементируем intCounter до 0 */
    if(intCounter) intCounter--;
    INTCONbits.T0IF = 0;   /* Сброс флага прерывания, по переполнению Таймера 0 */
}


void sendLetter(unsigned char index)
{
    unsigned char shift;
    shift = codes[index];
    do
    {
        LED_PIN = 1;
        if(shift & 0x80) //0x80 = 1000 0000
        {
            delay10tcy(DASH);
        }
        else
        {
            delay10tcy(DOT);
        }
        LED_PIN = 0;
        delay10tcy(DOT);
        shift <<= 1;
    } while(shift != 0x80);
    delay10tcy(DOT);
}   


void main(void)
{
    /* Устанавливаем порт LED как выход */
    LED_TRIS = 0;


    /*Включаем высокий и низкий уровень приоритета прерываний */
    RCON = 0;
    RCONbits.IPEN = 1;


    /* Настраиваем Таймер 0 */
    T0CONbits.T0PS0 = 0;
    T0CONbits.T0PS1 = 0;
    T0CONbits.T0PS2 = 1;


    T0CONbits.PSA = 0;
    
    T0CONbits.T0SE = 0;
    T0CONbits.T0CS = 0;
    T0CONbits.T08BIT = 1;
    T0CONbits.TMR0ON = 1;


    /* Включаем Таймер 0 с высоким приоритетом */ 
    INTCON2bits.T0IP = 1;
    INTCONbits.T0IE = 1;
    
    /* Включаем прерывания */
    INTCONbits.GIE = 1;
    //В вечном цикле отсвечиваем светодиодом Морзянку A, B, C, A, B, C и т.д.
    //пока МК не отключим
    while(1)
    {
      sendLetter(0);
      sendLetter(1);
      sendLetter(2);
    }
}

Мы люди грамотные по этому должны понимать, что для того, чтобы понять полностью эту программу нужно скачать Datasheet.
Для нас интересны следующие регистры и биты:
RCON{IPEN} - бит включения приоритета прерываний, если 1 уровни приоритета включены.


INTCON{GIE} - если RCON{IPEN} = 0, то это бит глобального разрешения прерываний 
1 - все прерывания разрешены, а если RCON{IPEN} = 1, то данным битом разрешаются только прерывания  с высоким приоритетом.
INTCON{T0IF} - по букве F понятно, что это флаг, кстати в даташите данный регистр описан как TMR0IF, если 1 произошло переполнение Таймер 0
INTCON{T0IE} - здесь буква E от слова ENABLES (включен). Показывает включено прерывание по переполнению Таймера 0 или нет. Если 1 то включено.
INTCON2{T0IP} - буква P от слова PRIORITY (приоритет). Если 1 то прерывание по переполнению Таймера 0 имеет высокий приоритет.
T0CON{TOPS0} - как и следующие три бита, это бит множителя,
T0CON{TOPS1}
T0CON{TOPS2}

111 = 1:256
110 = 1:128
101 = 1:64
100 = 1:32
011 = 1:16
010 = 1:8
001 = 1:4
000 = 1:2
Надеюсь вы помните, что самый правый бит 0, т.е в данном случае последовательность такая T0PS2:T0PS0

T0CON{PSA} - Если 1, то множитель отключен, т.е. будет без разницы чему равны верхних три бита все равно соотношение тактового сигнала и тактов счетчика 1:1.
T0CON{T0CS} - с этим у меня возникла проблема перевода, если правильно понял, то это выбор по какому пину получать тактовый сигнал, если 1 то по T0CKI он же RA4 иначе же по CLKO.
T0CON{T0SE} - по какому сигналу происходит инкремент счетчика от сигнала с пина
T0CKI, если 1 - то инкремент происходит при переходе с высокого уровня сигнала в низкий, иначе наоборот с низкого в высокий.
T0CON{T08BIT} - если единица то имеем дело с 8-битным счетчиком, иначе с 16-битным
T0CON{TMR0ON} - если 1, то Таймер 0 включен.

Теперь мы в курсе всех подробностей у нас включены приоритеты, работает Таймер 0 с 8-битным счетчиком, делитель 1:4, используется тактовый сигнал с CLK0.
Ну что ж пожалуй, на сегодня хватит. :)




1 комментарий:

  1. The King Casino: The New King & The World of Gaming
    The https://jancasino.com/review/merit-casino/ King Casino is the new place where the real money gambling novcasino is legal in Florida and Pennsylvania. We love febcasino.com the septcasino new casino. We've got some great

    ОтветитьУдалить