[pic16f88] Этап 12. Связь МК и компьютера посредством COM-порта

Дмитрий Филатов
Исследуем возможности модуля USART МК pic16f88.

Информация
Условия компиляции и прошивки аналогичны 8 этапу.

Примечание. Для связи микроконтроллера с компьютером должен использоваться прямой COM-COM кабель, который вернее всего спаять самому (соединить пины по номерам друг с другом).

Радиокомпоненты и оборудование
- микроконтроллер pic16f88
- регистр сдвига 74HC595
- символьный жидкокристаллический индикатор Winstar WH1602A-YGH-CTK (1602H REV.0)
- разъём DB-9 (мама)
- конвертер уровней TTL<->RS-232 MAX232
- операционный усилитель LM358N
- резисторы номиналом 18k, 2k, 100k
- фотодиод типа ФД256
- конденсаторы ёмкостью 1 мкф (5 шт)
- источник стабилизированного питания напряжением +5 В

Электрическая принципиальная схема
Исходный код прошивки на языке C
Файл pic.c, компилятор SDCC

#include <pic16f88.h>

__code char __at _CONFIG1 conf1 = _CP_OFF & _CCP1_RB0 & _DEBUG_OFF & _WRT_PROTECT_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _MCLR_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_IO;
__code char __at _CONFIG2 conf2 = _IESO_OFF & _FCMEN_OFF;

#define RS_SHCP RA1 //Сдвоенные сигналы: тактовый и тип данных
#define DS RA6
#define E_STCP RA0 //Сдвоенные сигналы: стробирующий и защёлка

void __delay_us(unsigned char t){
while(t--){
__asm
NOP
NOP
NOP
NOP
NOP
__endasm;
}
}

void __delay_ms(unsigned char m){
while(m--){
__delay_us(1000);
}
}

void lcd_write(unsigned char c, unsigned char sig_rs){
int i;
E_STCP = 0; //Здесь Е нас не интересует, может быть любым
for(i=7;i>=0;i--){
DS = (c >> i) & 1;
RS_SHCP = 0; //Здесь важна SHCP, RS может быть любым
RS_SHCP = 1; //-//-
}
E_STCP = 1; //Защёлкнули данные и выставили высокий уровень стробирующего сигнала. Интересуют оба.
RS_SHCP = sig_rs; //Теперь можем пренебрегать SH. Выставили нужный уровень сигнала RS
__delay_us(200); //Теперь имеем сигналы DS (DB7-DB0), для создания которых отработали SH и ST, и сигнал RS
E_STCP = 0; //Начинаем выполнять операцию записи в ЖКИ, STCP не интересует
}

//Рекурсивная функция преобразования типа int в тип char *
void __intToStr(int n, char* c, int l)
{
int i, j;
char t;
if (n == 0){
c[l++] = '\0';
for (i = 0, j = l-2; i<j; i++, j--) {
t = c[i];
c[i] = c[j];
c[j] = t;
}
return;
}
c[l++] = n % 10 + '0';
__intToStr(n/10, c, l);
}

void print_str(unsigned char *c){
unsigned char k;
while (k=*c++) {
lcd_write(k, 1);
}
}

void lcd_init(){
__delay_ms(15);

lcd_write(0b00111000, 0); //Шина 8 бит, 2 строки
__delay_ms(40);

lcd_write(0b00000001, 0); //Очистка экрана
__delay_ms(2);

lcd_write(0b00000110, 0); //Инкремент
__delay_ms(40);

lcd_write(0b00001100, 0); //Включили дисплей
__delay_ms(40);

lcd_write(0b00000001, 0); //Очистили дисплей
__delay_ms(2);
}

//Функция отправки байта в COM-порт
void SendByte(unsigned char byte){
while(!TXIF) continue;
TXREG = byte;
}

main(){
OSCTUNE = 0b00000000; // Калиброванная частота
OSCCON = 0b01100100; // 4MHz
CCP1CON = 0b00000000; // Отключить модуль CCP
ANSEL = 0b00010000; // AN4 - аналоговый вход
ADCON1 = 0b10000000; // Опорное напряжение - от источника питания, правое выравнивание
ADCON0 = 0b01100000; // Канал AN4, делитель /8, модуль выключен
CMCON = 0b00000111; // Отключить компаратор
TRISA = 0b00000000; // PORTA все как output
PORTA = 0b00000000; // Очистка PORTA (RA5:RA0)
TRISB = 0b00100100; //PORTB bit 5, bit 2 на вход (требование USART)
PORTB = 0b00000000; //Очистить PORTB

BRGH = 0; //Низкая скорость
SPBRG = 25; //2400 бод
SYNC = 0; //Асинхронный режим
SPEN = 1; //Включить порт
TXEN = 1; //Передача включена

lcd_init();

ADON = 1; //Включили модуль АЦП
__delay_ms(20); //Выждали необходимое время Taq

while(1){
unsigned int x;
char s[5]; //Для правого выравнивания: 4 цифры и символ завершения строки. Для левого должно быть char s[6]
GO_DONE = 1;
while(GO_DONE);
x = (ADRESH<<8) + ADRESL;
x = (x * 49)/10;
__intToStr(x,s,0); //Преобразование числового значения в строку
lcd_write(0b0000001, 0); //Очистка дисплея
print_str(s); //Вывод строки на дисплей
print_str(" mV");

SendByte(ADRESH); //Отправить старший байт
SendByte(ADRESL); //Отправить младший байт
__delay_ms(50); //Дать компьютеру 50 мкс на вычисления

__delay_ms(4);
}
}

Компьютерная программа
Для чтения COM-порта предлагается следующий код (IDE Borland C++ Builder 6). На форме расположены кнопки "Соединить" (Button1), "Разъединить" (Button2) и "Читать" (Button3), Memo1 (ScrollBars << ssVertical) и две метки Label1 и Label2.
Исходный код программы чтения COM-порта, C++

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit.h"
#include <windows.h>

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
DCB dcb;
HANDLE handle;
OVERLAPPED overlap;
COMMTIMEOUTS CommTimeOuts;
bool stop = true;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{

}

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
stop = true;

handle = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); //Указать используемый порт (COM1, COM2, COM3 etc.)

if(handle == INVALID_HANDLE_VALUE){
ShowMessage("Не удалось создать порт");
}

GetCommState(handle, &dcb);
dcb.BaudRate = CBR_2400; //2400 бод, столько же, сколько в прошивке МК
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if(!SetCommState(handle, &dcb)){
ShowMessage("Не удалось настроить порт");
}

CommTimeOuts.ReadIntervalTimeout = 1;
SetCommTimeouts(handle, &CommTimeOuts);

PurgeComm(handle, PURGE_RXCLEAR);
PurgeComm(handle, PURGE_TXCLEAR);
}

//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
stop = false;
CloseHandle(handle);
}

//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
BYTE buffer;
BYTE adresh;
BYTE adresl;
DWORD dwSize;
while(stop){
ReadFile(handle, &adresh, sizeof(adresh), &dwSize, &overlap);
ReadFile(handle, &adresl, sizeof(adresl), &dwSize, &overlap);

int x = (adresh<<8) + adresl;

Memo1->Lines->Add( "0x" + IntToHex(adresh, 2) );
Memo1->Lines->Add( "0x" + IntToHex(adresl, 2) );
Memo1->Lines->Add( "---" );

Label1->Caption = AnsiString().sprintf("%04d ", x);

float v = (float)x/1023*5;
Label2->Caption = AnsiString().sprintf("%.04f", v);

Application->ProcessMessages();
}
}

//---------------------------------------------------------------------------

UPD (11.02.2018) Конечно, этот код ужасен, как и сам C++ Builder 6; сейчас такие вещи легко делаются на Qt. Оставлю этот материал для истории.

А вот так выглядит схема, собранная на беспаечной макетной плате.
2013-12-06