Форум программистов, компьютерный форум, киберфорум
Наши страницы
Микроконтроллеры
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.52/166: Рейтинг темы: голосов - 166, средняя оценка - 4.52
Humanoid
Почетный модератор
9981 / 3867 / 348
Регистрация: 12.06.2008
Сообщений: 11,401
1

Cамоучители по программированию PIC

15.09.2010, 15:17. Просмотров 30316. Ответов 9

Решил я поделиться наработками своих программ для PIC контроллеров. Хотя тут я выкладываю примеры для работы с PIC18F2550, но эти примеры подойдут для любого контроллера (конечно, если хватит выводов и скорости для конкретных задач). Проблемы могут быть только с другими компиляторами (например, для PIC16 нет компилятора MCC... там обычно используют HT-PICC).
Всё что будет сказано в этой теме не претендует на правильность подходов и идеальную оптимальность. Это просто работает и может помочь другим разобраться в аналогичной проблеме.



Использование LCD
Я использую символьный ЖКИ MTC-16205D (2 строки, 16 столбцов). В даташите пишут, что он может работать от 2.7 до 5.5 вольт. Имеется 16 выводов:
1 - земля
2 - питание
3 - контрастность
4 - RS - режим работы (если задать 0, значит мы передаём команды, если задать 1, значит мы передаём данные)
5 - R/W - чтение/запись (если задать 0, значит мы записываем в ЖКИ, если 1, значит читаем). Лично я его намертво подключаю к земле... мне нечего читать из индикатора... я туда только записываю.
6 - E (по этому выводу мы будем передавать тактовые импульсы)
7-14 - DB0-DB7 (по этим выводам передаются сами данные/команды)
15,16 - питание для подсветки... в моём ЖКИ подсветки нет, поэтому я эти выводы не использую.

Cамоучители по программированию PIC


Несколько слов про контрастность... я использую делитель, что бы задать на 3 вывод низкое напряжение. Но можно этот вывод просто посадить на землю. В этом случае фон будет немного темноватым, но надписи будет видно.

Про выводы DB0-DB7... изначально ЖКИ запускается в 8-битовом режиме. Т.е. для обмена информации нужно использовать 8 линий данных. Но не всегда есть такое количество свободных выводов у PIC контроллера. Но к счастью этот ЖКИ (и многие, но не все) умеет работать в 4-битном режиме... в результате каждый бит передаётся за два раза. Вот такой режим мы и будем использовать. Для этого выводы 7-10 (DB0-DB3) сажаем на землю, а во время работы надо будет подать специальную команду, что бы перевести его в 4-битный режим. Работать будем только с выводами DB4-DB7.

Для написания программы для PIC контроллера я использую компилятор MPLAB C Compiler for PIC18 MCUs... или просто MCC18 (он платный, но есть бесплатная урезанная Standard-Eval Version).
Вот так выглядит исходник, который я использую:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#ifndef __LCD_H
#define __LCD_H
 
#include <p18f2550.h>
#include <pconfig.h>
#include <delays.h>
#include <string.h>
 
#define LCD_PIN_RS PORTCbits.RC6        // Вывод контроллера, к которому подключен вывод RS от ЖКИ
#define LCD_PIN_RS_TRIS TRISCbits.TRISC6    // Тот же вывод, только его TRIS (используется при инициализации)
#define LCD_PIN_E PORTBbits.RB4         // Вывод контроллера, к которому подключен вывод R от ЖКИ
#define LCD_PIN_E_TRIS TRISBbits.TRISB4     // Его TRIS
 
#define LCD_COL_NUM 16              // Количество столбцов в ЖКИ
 
#define LCD_RS_Instruction() LCD_PIN_RS=0;  // Режим передачи команд
#define LCD_RS_Data() LCD_PIN_RS=1;     // Режим передачи данных
 
#define Delay230ns  Delay1TCY();Delay1TCY();// Задержка 1 команд (83.3333333 нс, нужно минимум 230 нс... но тут выполняются ещё 2 команды... поэтому получается 250 нс)
#define Delay39us   Delay100TCYx(5);    // 500 команд = 41.6666666 мкс (нужно 39 мкс)
#define Delay43us   Delay100TCYx(6);    // 600 команд = 50.0000000 мкс (нужно 43 мкс)
#define Delay1530us Delay10KTCYx(2);    // 20000 команд = 1666 мкс (нужно 1530 мкс)
 
const unsigned char LCD_Chars[64] = {
    'A', 0xA0,'B',0xA1,0xE0, 'E',0xA3,0xA4,0xA5,0xA6, 'K',0xA7, 'M', 'H', 'O',0xA8,
    'P', 'C', 'T',0xA9,0xAA, 'X',0xE1,0xAB,0xAC,0xE2,0xAD,0xAE, 'b',0xAF,0xB0,0xB1,
    'a',0xB2,0xB3,0xB4,0xE3, 'e',0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD, 'o',0xBE,
    'p', 'c',0xBF, 'y',0xE4, 'x',0xE5,0xC0,0xC1,0xE6,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7
};
 
unsigned char LCD_cache[2][LCD_COL_NUM+3] = {{""},{""}};
 
void LCD_Init();            // Инициализация ЖКИ
void LCD_Clear_Display();   // Очистить экран
void LCD_GotoXY(const unsigned char row,const unsigned char col);   // Установить курсор на позицию
void LCD_Print_Char(unsigned char c);                               // Нарисовать один символ
void LCD_WriteXY(const unsigned char row,const unsigned char col,unsigned char *s,const unsigned char needfill);    // Вывести строку на экран в заданной строке и позиции
void LCD_Tick();        // Строб
void LCD_Send_Tetrad(unsigned char data);   // Отправить тетраду в ЖКИ
void LCD_Send_Byte(unsigned char data);     // Отправить байт в ЖКИ
 
void LCD_Tick()
{
    LCD_PIN_E = 1;
    Delay230ns;
    LCD_PIN_E = 0;
    Delay230ns;
}
 
void LCD_Send_Tetrad(unsigned char data)
{
    unsigned char tmp;
    tmp = PORTB; // Получаем текущее значение порта
    tmp = (tmp & 0xf0) | (data & 0x0f); // У текущего значения порта меняем только младшую тетраду
    PORTB = tmp; // Записывам новое значение в порт
    PORTB = data; // Записывам новое значение в порт
 
    PORTB = PORTB | 0b00010000;
    LCD_Tick(); // Строб
}
 
void LCD_Send_Byte(unsigned char data)
{
    unsigned char tmp;
    tmp = data >> 4; // Старшую тетраду в tmp
    LCD_Send_Tetrad(tmp); // Передаём тетраду
    tmp = data; // Малудшую тетраду в tmp
    LCD_Send_Tetrad(tmp); // Передаём
}
 
void LCD_GotoXY(const unsigned char row,const unsigned char col)
{
    unsigned char tmp;
    LCD_RS_Instruction(); // Передаём команды
    if (row==0) // Рассчитываем значение курсора по номеру строки и столбцу
        tmp=0x80+col;
    else
        tmp=0xc0+col;
    LCD_Send_Byte(tmp); // Устанавливаем позицию курсора
    LCD_RS_Data(); // Переходим в режим передачи данных
    Delay39us;
}
 
void LCD_Print_Char(unsigned char c)
{
    if (c>=0xC0)
        {c=LCD_Chars[c-0xC0];}
    else
    {
        if (c==0xA8)
            c=0xA2;
        else if (c==0xB8)
            c=0xB5;
        else if (c==0xB0)
            c=0xDF;
    }
    LCD_Send_Byte(c);
    Delay43us;
}
 
void LCD_WriteXY(const unsigned char row,const unsigned char col,unsigned char *s,const unsigned char needfill)
{
    unsigned char tmp;
    size_t len = strlen((const char *)s);  // Определяем длинну строки
    if (len>LCD_COL_NUM+2) len=LCD_COL_NUM+2; // На всякий случай
    if (memcmp((const void *)LCD_cache[row],(const void *)s,len)==0) return; // Сравниваем с кешем... если не изменилось, то просто выходим
    memcpy((void *)LCD_cache[row],(const void *)s,len); // Если изменилось, то вначале записываем новое значение в кеш
    LCD_GotoXY(row,col);
    tmp=col;
    while (*s)
    {
        if (*s)
            LCD_Print_Char(*s++); // Выводим строку на экран
        Delay43us;
        tmp++; // И заодно высчитываем позицию
        if (tmp>LCD_COL_NUM) break;
    }
    if (needfill) // Если указано needfill
    {
        for(;tmp<LCD_COL_NUM;tmp++)
        {
            LCD_Print_Char(0x20); // Заполнять пробелами до конца строки
        }
    }
}
 
void LCD_Init()
{
    LCD_PIN_RS_TRIS = 0;// Пины котроллера на запись в ЖКИ
 
    LCD_RS_Instruction();   // Перейти в режим команд
    
    LCD_Send_Tetrad(0x03); // Установить 8-битный режим... лучше это делать, что бы избежать глюков при перезагрузке
    Delay39us; // 700 команд = 58.3333333 мкс (нужно 39 мкс)
 
// Следующие 4 строки для избежания глюков
    LCD_Tick();
    Delay39us;
    LCD_Tick();
    Delay39us;
 
    LCD_Send_Tetrad(0x02); // Установить 4-битный режим
    Delay39us;
    LCD_Send_Byte(0x28); // 4-битный режим, 2 строки, шрифт 5x8 точек
    Delay39us;
    LCD_Send_Byte(0x08); // Выключить дисплей, выключить курсор, выключить моргание курсора
    Delay39us;
    LCD_Send_Byte(0x0f); // Включить дисплей, включить курсор, включить моргание курсора
    Delay39us;
    LCD_Send_Byte(0x06); // Курсор будет двигаться вправо при выводе текста
    Delay39us;
    LCD_RS_Data();      // Перейти в режим данных
}
 
void LCD_Clear_Display()
{
    LCD_RS_Instruction();   // В режим команд
    LCD_Send_Byte(0x01);    // Отправить команду 0x01 (очистка экрана)
    Delay1530us;            // Задержка 1.53 мс
    LCD_RS_Data();          // В режим данных
}
 
#endif
Обратите внимание на #define в начале текста. Изменяя их, вы можете заставить работать этот код на других контроллерах, с другими тактовыми частотами и подключенным LCD к другим выводам. Там же указано количество столбцов в ЖКИ (LCD_COL_NUM).

Первым делом, когда подключите этот файл, то укажите правильные #define:
LCD_PIN_RS - к какому выводу PIC'а подключен 4-й вывод ЖКИ (вывод RS)
LCD_PIN_RS_TRIS - тоже самое, но только TRIS (что бы можно было указать, что вывод используется на передачу)
LCD_PIN_E - к какому выводу подключен 6-й вывод ЖКИ (E)
LCD_PIN_E_TRIS - аналогично как и для LCD_PIN_RS_TRIS
LCD_COL_NUM - сколько столбцов имеет ЖКИ
Delay230ns - задержка не менее 230 нс (наносекунд)
Delay39us - задержка не менее 39 мкс (микросекунд)
Delay43us - задержка не менее 43 мкс (микросекунд)
Delay1530us - задержка не менее 1530 мкс (микросекунд)

У меня задержки рассчитаны для тактовой частоты 48 МГц (12 MIPS)... если частота работы вашего PIC'а отличается, то вам нужно пересчитать задержки под свою скорость.

Если вы подключили 4 линии данных (DB4-DB7) не на RB0-RB3, а на какие-то другие выводы, то вам ещё надо будет подредактировать функцию LCD_Send_Tetrad(), т.к. сейчас она работает только с этими выводами.

Немного про назначение функций:
LCD_Init();
Инициализация ЖКИ. При этом он переходит в 4-битный режим, устанавливает режим работы с 2 строками, выбирает движение курсора слева направо. Но помимо этого нужно указать TRISB (в данном примере) на передачу для четырёх воводов DB4-DB7. Например TRISB=0;

void LCD_Clear_Display();
Очистить содержимое. Должно работать, но лично я не пользуюсь. Я в функции LCD_WriteXY() всегда передаю needfill=1

void LCD_GotoXY(const unsigned char row,const unsigned char col);
Установить курсор на определённую позицию. row - это номер строки (нумеруется начиная с нуля), col - номер столбца (тоже нумеруется начиная с нуля). Обратите внимание, что в LCD_Init() отображение курсора отключено... т.е. он не будет отображаться.

void LCD_Print_Char(unsigned char c);
Нарисовать символ в том месте, где стоит курсор. При этом курсор сдвинется вправо на одну позицию.

void LCD_WriteXY(const unsigned char row,const unsigned char col,unsigned char *s,const unsigned char needfill);
Вывести строку на ЖКИ
row - номер строки (нумеруется начиная с нуля)
col - номер стролбца, откуда будет начинаться строка (нумеруется начиная с нуля)
s - указатель на саму строку... строка должна заканчиваться на символ '\0'
needfill - если его значение отлично от нуля, то оставшаяся часть строки будет заполнена пробелами до самого конца.
Можно передавать как латинские буквы, так и русские (в кодировке cp1251)... для этого используется массив LCD_Chars.
Эта функция запоминает каждую строку в массиве LCD_cache и не станет выводить её на ЖКИ, если там и так эта строка. Это сделано для предотвращения мерцания, когда непрерывно выводится одна и та же строка.

void LCD_Tick();
Строб. Даёт понять ЖКИ, что ему отправлены данные и их нужно принять. До тех пор, пока не будет выполнен LCD_Tick(), ЖКИ не будет игнорировать все данные, которые мы ему выставляем на выводах.

void LCD_Send_Tetrad(unsigned char data);
Отправить тетраду (4 бита) данных в ЖКИ. Т.е значения data могут быть от 0 до 15.

void LCD_Send_Byte(unsigned char data);
Отправить байт в ЖКИ. Тут нам не надо думать о том, что байт надо разбивать пополам... этим займётся сама функция.



Пример использования:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <p18f2550.h> // файл, в котором описаны особенности нашего PIC'а. Если у вас другой контроллер, то надо подключать другой файл
#include "lcd.h" // наш подключаемый файл с функциями для работы с ЖКИ
 
void main(void)
{
  char s[19] = "Работает"; // массив для строки
  unsigned char * p = &s; // указатель на этот массив
 
  TRISB=0; // все вывода PORTB на передачу
  LCD_Init(); // инициализировать ЖКИ
 
  LCD_WriteXY(0,0,p,1); // первая строка
  s[0]='в';
  s[1]='р';
  s[2]='о';
  s[3]='д';
  s[4]='е';
  s[5]='\0';
  LCD_WriteXY(1,0,p,1); // вторая строка
  while(1); // зависаем
}

Вот так выглядит сам ЖКИ:
Cамоучители по программированию PIC


Краткое описание на этот ЖКИ:
MTC-16205D.pdf

Более полное описание от производителя
MTC-S16205DFYHSAY.pdf

Описание на контроллер HD44780U, на основе которого сделан этот (и многие другие) ЖКИ:
HD44780U.pdf
9
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
15.09.2010, 15:17
Ответы с готовыми решениями:

Дизассемблер PIC
Есть прошивка на ПИК http://www.obddiag.net/adaptir/obdcan2ec.hex для...

Micro C for PIC
Только с ним начал. Поморгал светодиодами. До этого писал программы для АВР в...

mikroPascal for PIC
Господа, а кроме SWK здесь кто-нибудь пользуется микропаскалем для PIC? Как...

flash в pic
Акакже записывать, подскажите нубу ссылочку

PIC программатор
Собираюсь делать pic программатор,нашел...

9
Humanoid
Почетный модератор
9981 / 3867 / 348
Регистрация: 12.06.2008
Сообщений: 11,401
16.09.2010, 14:28  [ТС] 2
Обмен данными по шине I2C на примере часов DS1307
Существует много внешних устройств, управление которыми осуществляется по шине I2C (она же IIC). У многих PIC контроллеров есть аппаратная поддержка этой шины. Но я не подумав о будущем, занял эти выводы под другие нужды (под LCD, о котором писал выше... на PIC18F2550 аппаратный I2C находится на RB0, RB1). Тут я хочу рассказать, как можно программно работать с шиной I2C.

Немного теории
I2C - это двухпроводная шина, которая предназначена для работы внутри устройств (на сколько я знаю, изначально разработана фирмой Philips для работы внутри телевизоров). И хотя я слышал, что некоторые люди используют её для связи на десятки метров, всё же это не желательно. Шина состоит из двух проводов: SDA (линия данных) и SCL (тактовые импульсы). Обе линии притянуты к питанию (в нашем случае к 5 вольтам) резисторами по 5 кОм каждый. На одной шине может находится одно ведущее (master) устройство (в нашем случае PIC контроллер) и много ведомых (slave) устройств (можно адресовать до 128 устройств)... в данном случае будет только одно ведомое устройство - RTC (Real-Time Clock (часы реального времени)) DS1307. Шина I2C может работать на двух скоростях: 100 кГц и 400 кГц. Но так как DS1307 поддерживает только 100 кГц, то все задержки у меня стоят в расчёте на эту скорость.

Как происходит обмен данными
В состоянии покоя выводы PIC'а настроены на приём... при этом резисторы притягивают обе линии к логической единице. Что бы начать обмен данными, ведуще устройство вначале передаёт "СТАРТ". Для этого SCL остаётся нетронутым (притянутый к единице), а SDA при этом переводится на передачу и устанавливается в ноль. Удерживаем всё в таком состоянии не менее 4 мкс и устанавливаем в ноль ещё и SCL. Через 4 мкс после этого ведущее устройство может передавать адрес устройства, с которым хочет общаться.

Данные (в том числе и адрес ведомого устройства) передаются по 8 бит. Для этого ведущий устанавливает SDA в ноль или отпускает его, что он стал единицей. После чего на 4 мкс отпускает SCL. И через 4.7 мкс опять прижимает его к нулю. И так для каждого бита. Биты передаются начиная со старшего.
После каждого переданного байта, ведомое устройство передаёт один бит подтверждения. Для этого тоже нужно будет "дёрнуть" один раз SCL.
Если ведущий читает данные из ведомого, то ведущий тоже должен посылать подтверждение. Но только не для последнего байта. Если ведущий не хочет больше получать данные, то он уже не посылает подтверждение, а посылает сигнал "СТОП".

После обмена данными ведущее устройство посылает сигнал "СТОП". Для этого устанавливает SDA в ноль (SCL и так в нуле). Удерживает так 4.7 мкс, после чего отпускает SCL (при этом резистор притягивает его к единице) и ещё через 4.7 мкс отпускает и SDA.

Про адресацию
Сразу после сигнала "СТАРТ" ведущее устройство передаёт один байт адреса устройства, с которым хочет общаться. Например, для DS1307 этот адрес задан жёстко: 1101000x. Старшие 7 бит - это и есть адрес, а младший бит - это направление. Если мы его передадим как ноль, это значит, что мы будем записывать в устройство. Если это будет единица, значит устройство будет передавать данные.

Ещё важное замечание
Байты читаются и записываются последовательно. Сигнал "СТОП" не означает, что указатель сбрасывается в ноль. Что бы сбросить указатель, нужно обратиться к устройству для записи и передать один нулевой байт. Т.е. если мы обратились к устройству для записи, то следующий байт - это будет указатель на ячейку памяти, начиная с которой мы хотим записывать данные. Если мы связываемся с устройством для чтения, то мы не передаём никаких данных (только адрес)... поэтому устройство будет передавать данные начиная с последней позиции.
В общем, что бы читать начиная с самого начала, нам нужно вначале связаться с устройством для записи (младший бит=0 ), передать байт 0x00, передать СТОП, а после этого уже опять передаём СТАРТ, адрес для чтения (младший бит=1 ) и уже читаем данные.
Вот как это расписано в даташите:

Записываем в устройство:
Cамоучители по программированию PIC


Читаем из устройства
Cамоучители по программированию PIC



О самих часах DS1307
К микросхеме подключается обычный часовой кварц на 32 кГц (я его выпаял из старой материнской платы от первого пентиума). Так же есть возможность подключать батарейку на 3 вольта (я взял её из той же самой материнской платы вместе с удобной пластмассовой фигнёй, в которую эта батарейка вставляется). Батарейка обеспечивает работу часов даже при выключенном питании. Можно её вообще не подключать, но тогда при каждом выключении питания придётся заново устанавливать дату и время. В режиме работы от батарейки в микросхеме работают только сами часы... принимать или передавать данные она в при этом не сможет (да и если +5 В нету, то и некому эти данные передавать). Когда выключено питание, то часы тянут с батарейки не больше 500 нА... это очень маленький ток. Поэтому батарейки хватит на несколько лет.
Cамоучители по программированию PIC


Так же у этой микросхемы есть 7 вывод SQW/OUT... можно настроить, что бы микросхема "дёргала" этим выводом с частотой 1 Гц, 4 кГц, 8 кГц или 32 кГц. Это можно использовать, например, для внешнего прерывания для PIC'а. Но я этот вывод не использую (в основном из-за нехватки свободных выводов у PIC'а).

Описание на DS1307
DS1307.pdf

У микросхемы есть 8 регистров (7 для времени и даты и один для настроек вывода SQW/OUT).
Cамоучители по программированию PIC



Числа хранятся в этих регистрах в причудливом виде: в младшей тетраде (4 бита) хранятся единицы, а в старшей - десятки. Старший бит в секундах (нулевой регистр) - это запуск часов. Если этот бит установлен в 1, то часы не будут идти. Что бы запустить часы, этот бит надо установить в ноль. Время может быть либо в 12-часовом варианте, либо в 24-часовом... я везде ориентируюсь на 24-часовой.

DS1307 не умеет сам рассчитывать день недели. Он умеет только его увеличивать до 7, после чего сбрасывает в 1. Поэтому я рассчитываю день недели программным путём.

Значение года может быть от 0 до 99. Значит, 2000 придётся прибавлять самим.

Вот сам исходник:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#ifndef __DS1307_H
#define __DS1307_H
 
#include <p18f2550.h>
#include "delay_time.h"
 
#define I2C_SDA PORTCbits.RC2
#define I2C_SDA_TRIS TRISCbits.TRISC2
#define I2C_SCL PORTCbits.RC1
#define I2C_SCL_TRIS TRISCbits.TRISC1
 
#define I2C_Recive_mode() I2C_SDA_TRIS=1
#define I2C_Transmit_mode() I2C_SDA_TRIS=0
 
typedef struct
{
    unsigned char year;
    unsigned char month;
    unsigned char day;
    unsigned char hour;
    unsigned char min;
    unsigned char sec;
    unsigned char dow;
} TDateTime;
 
TDateTime datetime;
 
void CalcDoW();
void I2C_SendStart();
void I2C_SendStop();
char I2C_SendByte(unsigned char d);
unsigned char I2C_ReadByte(char last);
unsigned char RTC_GetDateTime();
unsigned char RTC_SetDateTime();
 
void CalcDoW()
{
    unsigned char a,m;
    int y;
    a = (14 - datetime.month) / 12;
    y = datetime.year+2000 - a;
    m = datetime.month + 12 * a - 2;
    datetime.dow = ((6999 + (datetime.day + y + y / 4 - y / 100 + y / 400 + (31 * m) / 12)) % 7) + 1;
}
 
char I2C_SendStart()
{
    Delay10us();
    I2C_Transmit_mode();
    I2C_SDA = 0;
    Delay10us();
    I2C_SCL = 0;
    I2C_SCL_TRIS = 0;
    Delay10us();
}
 
void I2C_SendStop()
{
    I2C_Transmit_mode();
    I2C_SDA = 0;
    Delay10us();
    I2C_SCL_TRIS = 1;
    Delay10us();
    I2C_Recive_mode();
    Delay10us();
}
 
void I2C_SendByte(unsigned char d)
{
    unsigned char tmp;
    char i;
    tmp = d;
    I2C_SDA = 0;
    for (i=1;i<=8;i++) // Перебираем все биты
    {
        if ((tmp & 0x80) != 0) // Устанавливаем значение старшего бита
            I2C_Recive_mode();
        else
        {
            I2C_Transmit_mode();
            I2C_SDA = 0;
        }
        Delay10us(); // Пауза 1 мкс
        I2C_SCL = 1; // Строб
        Delay10us();
        I2C_SCL = 0;
        Delay10us();
        tmp = tmp << 1; // Сдвигаем байт, что бы был на очереди следующий бит
    }
    I2C_Recive_mode(); // Переходим в режим приёма
    I2C_SDA = 1;
    Delay10us();
    I2C_SCL = 1; // Строб
    Delay10us();
    tmp = I2C_SDA; // Запаминаем значение
    I2C_SCL = 0;
    Delay10us();
 
    I2C_Transmit_mode(); // В режим передачи
    I2C_SDA = 0;
    if (tmp) // Если SDA был подтянут резистором к единице, значит устройство не ответило... а если в 0, значит всё в порядке
        return 1;
    else
        return 0;
}
 
unsigned char I2C_ReadByte(char last)
{
    unsigned char r = 0;
    char i;
    Delay10us();
    I2C_Recive_mode();
    Delay10us();
    for (i=1;i<=8;i++)
    {
        r = r << 1;
        I2C_SCL = 1;
        Delay10us();
        if (I2C_SDA) r = r | 0x01;
        I2C_SCL = 0;
        Delay10us();
    }
    Delay10us();
    if (last==0)
    {
        I2C_Transmit_mode(); // В режим передачи
        I2C_SDA = 0;
 
        I2C_SCL = 1;
        Delay10us();
        I2C_SCL = 0;
        Delay10us();
    }
    
    return r;
}
 
unsigned char RTC_GetDateTime()
{
    unsigned char i = 0;
    unsigned char pm;
    while ((!I2C_SDA) || (!I2C_SCL))
    {
        Delay1msx(3);
        Delay10usx(3);
        i++;
        if (i>100)
            return 1;
    }
    I2C_SendStart();
    if (I2C_SendByte(0xD0)) {I2C_SendStop();return 2;}
    if (I2C_SendByte(0x00)) {I2C_SendStop();return 2;}
    I2C_SendStop();
 
    I2C_SendStart();
    if (I2C_SendByte(0xD1)) {I2C_SendStop();return 2;}
 
    i = I2C_ReadByte(0); // секунды
    datetime.sec = i & 0x0f;
    i = i & 0x70;
    i = i >> 4;
    i = i * 10;
    datetime.sec = datetime.sec + i;
 
    i = I2C_ReadByte(0); // минуты
    datetime.min = i & 0x0f;
    i = i >> 4;
    i = i * 10;
    datetime.min = datetime.min + i;
 
    i = I2C_ReadByte(0); // часы
    datetime.hour = i & 0x0f;
    i = i >> 4;
    if ((i & 0x04) == 0) // 24-часовой режим
    {
        i = i & 0x03;
        i = i * 10;
        datetime.hour = datetime.hour + i;
    } else // 12-часовой режим
    {
        i = i & 0x03;
        if ((i & 0x02) == 0) pm = 0; else pm = 1;
        i = i & 0x01;
        i = i * 10;
        datetime.hour = datetime.hour + i;
        if (pm) datetime.hour += 12;
        if (datetime.hour==24) datetime.hour = 0;
    }
 
    i = I2C_ReadByte(0); // день недели
    datetime.dow = i & 0x07;
 
    i = I2C_ReadByte(0); // день
    datetime.day = i & 0x0f;
    i = i >> 4;
    i = i & 0x03;
    i = i * 10;
    datetime.day = datetime.day + i;
 
    i = I2C_ReadByte(0); // месяц
    datetime.month = i & 0x0f;
    i = i >> 4;
    i = i & 0x01;
    i = i * 10;
    datetime.month = datetime.month + i;
 
    i = I2C_ReadByte(1); // год
    datetime.year = i & 0x0f;
    i = i >> 4;
    i = i & 0x0f;
    i = i * 10;
    datetime.year = datetime.year + i;
    
    I2C_SendStop();
    return 0;
}
 
unsigned char RTC_SetDateTime()
{
    unsigned char i = 0;
    while ((!I2C_SDA) || (!I2C_SCL))
    {
        Delay1msx(3);
        Delay10usx(3);
        i++;
        if (i>100)
            return 1;
    }
    I2C_SendStart();
    if (I2C_SendByte(0xD0)) {I2C_SendStop();return 2;}
    if (I2C_SendByte(0x00)) {I2C_SendStop();return 2;}
 
    i = datetime.sec / 10;
    i = i << 4;
    i = i + (datetime.sec % 10);
    i = i & 0x7f;
    if (I2C_SendByte(i)) {I2C_SendStop();return 2;}
 
    i = datetime.min / 10;
    i = i << 4;
    i = i + (datetime.min % 10);
    i = i & 0x7f;
    if (I2C_SendByte(i)) {I2C_SendStop();return 2;}
 
    i = datetime.hour / 10;
    i = i << 4;
    i = i + (datetime.hour % 10);
    i = i & 0x3f;
    if (I2C_SendByte(i)) {I2C_SendStop();return 2;}
 
    CalcDoW();
    i = datetime.dow;
    if (I2C_SendByte(i)) {I2C_SendStop();return 2;}
 
    i = datetime.day / 10;
    i = i << 4;
    i = i + (datetime.day % 10);
    i = i & 0x3f;
    if (I2C_SendByte(i)) {I2C_SendStop();return 2;}
 
    i = datetime.month / 10;
    i = i << 4;
    i = i + (datetime.month % 10);
    i = i & 0x1f;
    if (I2C_SendByte(i)) {I2C_SendStop();return 2;}
 
    i = datetime.year / 10;
    i = i << 4;
    i = i + (datetime.year % 10);
    if (I2C_SendByte(i)) {I2C_SendStop();return 2;}
 
    if (I2C_SendByte(0)) {I2C_SendStop();return 2;}
 
    I2C_SendStop();
        return 0;
}
 
#endif
Как и в случае с ЖКИ, тут я тоже вначале использую #define, что бы указать порты, к которым подключена шина I2C.
I2C_SDA - вывод, к которому подключен SDA
I2C_SDA_TRIS - TRIS для этого вывода
I2C_SCL - вывод, к которому подключен SDA
I2C_SCL_TRIS - TRIS для этого вывода

И не забудьте пересчитать задержки, т.к. у меня всё настроено на PIC18F2550, у которого тактовая частота 48 МГц (12 MIPS). Кстати, функции с задержками я храню в отдельном подключаемом файле delay_time.h
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/*
 
  Все задержки расчитаны на тактовую частоту 48 МГц (12 мипсов)
 
*/
 
#ifndef __DELAY_TIME_H
#define __DELAY_TIME_H
 
#include <delays.h>
 
// 1 мкс = 12 команд
#define Delay1us() {Nop();Nop();Nop();Nop();Nop();Nop();Nop();Nop();Nop();Nop();Nop();Nop();}
 
// 10 мкс = 120 команд
#define Delay10us() {Delay100TCYx(1);Delay10TCY();Delay10TCY();}
 
// 100 мкс = 1.200 команд
#define Delay100us() {Delay100TCYx(12);}
 
// 1 мс = 12.000 команд
#define Delay1ms() {Delay1KTCYx(12);}
 
// 10 мс = 120.000 команд
#define Delay10ms() {Delay10KTCYx(12);}
 
// 100 мс = 1.200.000 команд
#define Delay100ms() {Delay10KTCYx(120);}
 
// Точно
void Delay1usx(unsigned char n);
 
// Почти точно
void Delay10usx(unsigned char n);
 
// Не так точно (где-то по 6 команд больше (0.5 мкс) на каждые 100 мкс)... но при условии, что Delay100us() точная
void Delay100usx(unsigned char n);
 
 
// Почти точно (где-то по 6 команд больше (0.5 мкс) на каждую 1 мс)... но при условии, что Delay1ms() точная
void Delay1msx(unsigned char n);
 
// Почти точно (где-то по 6 команд больше (0.5 мкс) на каждые 10 мс)... но при условии, что Delay10ms() точная
void Delay10msx(unsigned char n);
 
// Почти точно (где-то по 6 команд больше (0.5 мкс) на каждые 100 мс)... но при условии, что Delay100ms() точная
void Delay100msx(unsigned char n);
 
 
 
void Delay1usx(unsigned char n)
{
    n--;
    n--; // Уменьшили на 2, что бы компенсировать задержки цикла и т.п.
    while (n>0)
    {
        n--;
        Nop();Nop();Nop();Nop();
    }
    Nop();
}
 
void Delay10usx(unsigned char n)
{
    while (n>0)
    {
        n--;
        Delay100TCYx(1);Delay10TCY();// Убрали 10 тактов задержку, что бы компенсировать цикл
        Nop();Nop();// Т.к. убрать надо было не 10, а только 8 тактов, то добавляем 2 команды
    }
}
 
void Delay100usx(unsigned char n)
{
    while (n>0)
    {
        n--;
        Delay100us();
    }
}
 
void Delay1msx(unsigned char n)
{
    while (n>0)
    {
        n--;
        Delay1ms();
    }
}
 
void Delay10msx(unsigned char n)
{
    while (n>0)
    {
        n--;
        Delay10ms();
    }
}
 
void Delay100msx(unsigned char n)
{
    while (n>0)
    {
        n--;
        Delay100ms();
    }
}
 
#endif
Но в программе используются только Delay10us() и Delay10usx().

Теперь о самих функциях...
void CalcDoW()
Эта функция рассчитывает текущий день недели. 1=понедельник, 2=вторник... и т.д. Эта функция ничего не возвращает, а работает с глобальной переменной datetime. Функция сама вызывается внутри RTC_SetDateTime().

I2C_SendStart()
Передать "СТАРТ" на шину I2C.

I2C_SendStop()
Передать "СТОП" на шину I2C.

I2C_SendByte(unsigned char d)
Отправить байт на шину. Если всё прошло нормально и ведомое устройство ответило битом подтверждения, то вернёт 0. Если подтверждения не было, то вернёт 1.

I2C_ReadByte(char last)
Прочитать байт из шины I2C. Параметр last указывает, последний ли это байт или нет. Если после этого мы будем ещё читать, то last должен быть равен 0. В этом случае, PIC будет посылать бит подтверждения после принятия байта. Если last не равен нулю, значит мы больше не будем ничего читать и подтверждение не посылаем.
Функция возвращает байт, который был получен с шины.

RTC_GetDateTime()
Функция для работы с DS1307. Эта функция получает дату и время и помещает их в глобальную переменную datetime. Возвращает 0, если всё прошло нормально. Если шина занята и никак не освобождается, то возвращает 1. Если не пришло подтверждение от часов, то возвращает 2.

RTC_SetDateTime()
Функция для работы с DS1307. Эта функция записывает дату и время из переменной datetime в часы. В случае успеха возвращает 0.
Перед вызовом этой функции нужно установить всё внутри datetime, кроме dow (день недели). dow будет рассчитан сам с помощью функции CalcDoW().


Пример использования:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <p18f2550.h> // файл, в котором описаны особенности нашего PIC'а. Если у вас другой контроллер, то надо подключать другой файл
#include "ds1307.h" // наш подключаемый файл с функциями для работы с I2C и DS1307
 
void main(void)
{
  I2C_SDA_TRIS=1; // линию SDA на передачу... будет притянута резистором к единице
  I2C_SCL_TRIS=1; // линию SCL на передачу... будет притянута резистором к единице
 
  datetime.year=10;
  datetime.month=9;
  datetime.day=16;
  datetime.hour=14;
  datetime.min=27;
  datetime.sec=55;
  RTC_SetDateTime(); // устанавливаем время
 
// что-то делаем
 
  RTC_GetDateTime(); // получаем время
// теперь в datetime будет обновлённое время.
}

Вот так выглядит сама микросхема с кварцем (слева) и батарейка (справа)
Cамоучители по программированию PIC
10
Baltika3
0 / 0 / 0
Регистрация: 26.09.2012
Сообщений: 6
27.03.2013, 11:14 3
День добрый,господа. Подскажите какие-нибудь разумные самоучители по ПИКам. И если кто знает,где можно скачать продолжение самоучителя Корабельникова Е.А.(именно продолжение,а не практикум и абонемент)может у кого есть?
0
raxp
10186 / 6569 / 492
Регистрация: 28.12.2010
Сообщений: 21,166
Записей в блоге: 1
27.03.2013, 15:59 4
1- закрепленная тема по микроконтроллерам и ПЛИС в разделе электроники содержит также литературу по PIC
2- абонемент придуман сотоварищами Корабельникова не просто так
3- дополнить можно парой книжек (Кениг. Полное руководство по PIC-микроконтроллерам, Катцен. PIC-микроконтроллеры. Все что вам необходимо знать)
4- также не забываем русский сателлит сайта Микрочипа + PIC24.RU
0
russo turisto
97 / 92 / 0
Регистрация: 24.04.2010
Сообщений: 275
27.03.2013, 16:03 5
Лично я не могу читать Корабельникова, столько воды налил, какое терпение нужно иметь читая его. И это не только мое мнение.

Сид Кадцен PIC микроконтролеры все что необходимо знать , книга 2 - это классика по пикам.
Далее книга по 18 пикам http://forcoder.ru/other/primenenie-...assemblera-950
Ну и книги по PIC24 Луи Джасио и Магда. А лучше читать в оригинале, т.к. эти все книги переводы.
0
Baltika3
0 / 0 / 0
Регистрация: 26.09.2012
Сообщений: 6
28.03.2013, 04:51 6
согласен с russo turisto. Корабельников много воды льёт, но зато доходчиво объясняет суть вещей(самоучитель и должен быть таким). А тот же Сид Катцен, не спорю, на голову выше, но он больше похож на справочник для опытных бойцов. Для въезда в пики с нуля в самый раз Корабельников.

Тов. raxp, а почему абонемент придуман не просто так?Что это значит?
0
russo turisto
97 / 92 / 0
Регистрация: 24.04.2010
Сообщений: 275
30.03.2013, 18:53 7
Попалось высказывание про Корабельникова на мелкочипе, интересно почитать мнение dosikusa
0
Миниатюры
Cамоучители по программированию PIC  
raxp
30.03.2013, 19:07
  #8

Не по теме:

...ну, на казусе то же самое.

0
tolikvoron
6 / 6 / 0
Регистрация: 21.06.2010
Сообщений: 21
02.04.2013, 18:14 9
"согласен с russo turisto. Корабельников много воды льёт".... Кому не нравится, тот и не читает. Мне не нравятся статьи о заготовке древесины в 18-ом веке.....
0
Ethereal
4852 / 1874 / 243
Регистрация: 17.02.2013
Сообщений: 2,787
10.11.2016, 23:02 10
Если бы он еще ТУ воду лил.

Существуют также переходы с условием (условные переходы), то есть, с задействованием
так называемого стека.

...
Начинающим, в первую очередь, необходимо обратить внимание на команду безусловного
перехода GOTO.
Сначала нужно освоить ее, а потом переходить к изучению команды условного перехода
CALL.


(С)Корабельников

И так на протяжении всей книги.

Чего я натерпелся, знает только один Господь Бог: помощи никакой и пришлось рассчитывать только на свои силы.
- это Корабельников изучал микроконтроллеры. В итоге такого самостоятельного изучения и варки в собственном соку некоторые понятия в голове у Корабельникова встали криво. Подозреваю, что в процессе чтения книжки по КР580. Ибо только в этой архитектуре (ах, да еще в Z80) есть условные вызовы подпрограмм и возвраты из них. И это была бы не беда, что криво, но он своей книгой начал навоз из своей головы перегружать в головы других. И это уже плохо.
0
10.11.2016, 23:02
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
10.11.2016, 23:02

Програмирование PIC
ТАкое дело программа получает команды из текстового файла через RCREG но читает...

AVR vs PIC
Я програмист, в электронике разбираюсь слабо. Решил изучить микроконтроллеры...

Осваиваю PIC
Решил немного освоить пики. Думаю начать с ситемы команд и архитектуры. Может...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
10
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru