Наступила очередь рассказать о довольно интересной теме — о том, как подключить AVR микроконтроллер к компьютеру. Для этого, у микроконтроллеров есть приемопередатчик. У некоторых их, даже два.
Update:10.02.17
Использовать будем UART наиболее простой и распространенный интерфейс. Чтобы понять как он работает, представьте себе что уарт это водопроводный кран, из которого течет вода — байты. Каждая новая капля, «затирает» старую, поэтому главная задача нашей программы успевать забирать данные до того, как придут новые.
Существует два основных способа, это все время в цикле проверять наличие новых данных по флагам или настроить прерывание, по приходу нового байта. Если программа не использует задержки и скорость передачи низкая, то можно использовать первый способ, в остальных случаях, лучше использовать прерывания.
Данные приходят последовательно один байт за другим, поэтому часто UART называют последовательным портом. Если ваша программа достаточно простая, то можно принять/передать один байт и на основании этого выполнить какое то действие. Например если приняли байт 100, то включили светодиод.
if(byte == 100) { PORTB.1 = 1; } |
Только не стоит путать ASCII символ и байт, например i = ‘1’ i = 1 не одно и тоже. В чем разница можно почитать тут
Если ваша программа подразумевает, нечто более сложное, т.е. прием нескольких байт, то в прерывании по приходу данных складываем их в массив, далее в основном цикле разбираем массив. Стоит понимать, что поток не имеет ни начала, ни конца, он просто льется постоянно, поэтому нужно самому придумать какие то условности которые бы говорили о том, что сейчас пришел первый байт, а сейчас последний. Типовое решение, это использование спецсимволов, либо таймаутов. Пример можно посмотреть тут
Теперь перейдем к железу. У Atmega8 всего один приемопередатчик, PD0 — Rx, receiver (приемник) и PD1 — Tx, transmitter (передатчик).
Аналогичные ножки есть на нашем переходнике FT232. Соединяем микроконтроллер и переходник между собой. В протеусе это будет выглядеть так:
Напишем программу, которая по сигналу от компьютера будет включать светодиод и выключать. В CodeVision создаем новый проект, микроконтроллер atmega8, частота 8МГц (тактирование от кварца), на закладке USART включаем приемник и передатчик.
Обратите внимание, при текущей частоте микроконтроллера и скорости передачи, ошибка составляет 0,2%, т.е. данные могут теряться, но вероятность этого крайне низкая. Порт B настроим как выход — к нему подключим светодиод.
#include <mega8.h> #include <stdio.h> void main(void) { char data; PORTB=0x00; DDRB=0xFF; PORTD=0x00; DDRD=0x00; // USART initialization // Communication Parameters: 8 Data, 1 Stop, No Parity // USART Receiver: On // USART Transmitter: Off // USART Mode: Asynchronous // USART Baud Rate: 9600 UCSRA=0x00; UCSRB=0x10; UCSRC=0x86; UBRRH=0x00; UBRRL=0x33; while (1) { data=getchar(); if(data=='1') { PORTB=0xFF; } if(data=='0') { PORTB=0x00; } }; } |
В текущем алгоритме используется простой способ функция getchar() постоянно проверяет не появились ли новые данные. После того как данные пришли — записываем их в переменную data; если пришла единичка, зажигаем светодиод; если 0, то выключаем его. Еще раз обратите внимание ‘1’ означает ASCII символ 1, реально байт будет равен 0x31. Сделано так, потому что большинство терминалов при нажатии кнопки на клавиатуре, отправляет именно символы.
Собираем схемку и прошиваем. Подключаем к компьютеру. Запускаем программу KeTerm или другой терминал. Подсоединяемся к нужному com порту. Шлем единичку — светодиод включается, шлем 0 светодиод выключается.
Урок был бы неполным, если не применить наши знания по C#.
Создадим проект, нарисуем 2 кнопки и последовательный порт
В свойствах последовательного порта не забудьте настроить PortName, он должен соответствовать номеру порта переходника FT232. Добавим события: по клику на 1 кнопку — отсылаем 1, по клику на 2 кнопку — отсылаем 0, при загрузке формы — открытие порта, при выходе из формы — закрытие порта.
private void button1_Click(object sender, EventArgs e) { serialPort1.WriteLine("1"); } private void button2_Click(object sender, EventArgs e) { serialPort1.WriteLine("0"); } private void Form1_Load(object sender, EventArgs e) { serialPort1.Open(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { serialPort1.Close(); } |
И напоследок, видео работы всего этого безобразия)
Update: По просьбе Евгения программа немного изменена, теперь при запуске программа автоматически проверяет все ком порты, если есть активные, то они заносятся в comboBox1.
1 2 3 4 5 6 | private void Form1_Load(object sender, EventArgs e) { string[] myPort; //создаем массив строк myPort = System.IO.Ports.SerialPort.GetPortNames(); // в массив помещаем доступные порты comboBox1.Items.AddRange(myPort); //теперь этот массив заносим в список(comboBox) } |
Исходники программы
Для выделения памяти 2 элементов в массиве использовал конструкцию:
byte[] buf = new byte[2];
Если вдуг кому нибудь понадопбиться.
Делаете успехи
Спасибо, стараемся.. ) Тот кто ищет всегда найдет )
Добрый день. Скомпилировал Вашу прошивку и залил в контроллер. Программой для пк воспользовался тоже Вашей. Но, увы, ничего не заработало. С чем это может быть связано? usb-uart преобразователь CP2102.
с чем угодно, не правильно подсоединено, не правильные скорости и т.п. земли мк и переходника обязательно соединять
Подскажите, как с Вами можно связаться — Вконтакте и т.д. Буду очень признателен.
https://vk.com/a_telezhkin
у меня вопрос.через UART можно передавать а также принимать с компьютера .Одно временно нельзя.А если мне нужно со схемы в комп принять информацию а потом передать что я должен сделать?
Заранее говорю спасибо.
Если у вас полный дуплекс, то линии Rx,Tx независимые, поэтому прием никак не зависит от передачи.
System.IO.Ports.SerialPort.GetPortNames(); — А это в каком фреймвёрке эта функцияия есть?
Спасибо за статью, с терминалами обменяться данными получилось.
Можно ли сделать так, чтобы программа (для компьютера) отправляла не ASCII код, а просто числа?
можно
красавчик, помоги! пенсионеру. Надо с меги16, в CVAVR отправить unsigned int в C# по UART. Примерно понимаю что надо както разбить на байты, отправить, получить и както склеить.Хоть два кусочка с кодами : разбить-отправить и получить-склеить
легкого пути нет http://avr-start.ru/?p=2230
знаю язык Cи как мой трёхлетний сосед русский. Ну вот слепил рабочий код
CVAVR:
type : ATmega16A
Program type : Application
AVR Core Clock frequency: 4,000000 MHz
Memory model : Small
External RAM size : 0
Data Stack size : 256
*******************************************************/
// Standard Input/Output functions
#include
#include
#include
unsigned int MSBLSB = 12345;
unsigned char MSB,LSB;
void main(void)
{
DDRA=(0<<DDA7) | (0<<DDA6) | (0<<DDA5) | (0<<DDA4) | (0<<DDA3) | (0<<DDA2) | (0<<DDA1) | (0<<DDA0);
PORTA=(0<<PORTA7) | (0<<PORTA6) | (0<<PORTA5) | (0<<PORTA4) | (0<<PORTA3) | (0<<PORTA2) | (0<<PORTA1) | (0<<PORTA0);
DDRB=(0<<DDB7) | (0<<DDB6) | (0<<DDB5) | (0<<DDB4) | (1<<DDB3) | (0<<DDB2) | (1<<DDB1) | (0<<DDB0);
PORTB=(0<<PORTB7) | (0<<PORTB6) | (0<<PORTB5) | (0<<PORTB4) | (0<<PORTB3) | (1<<PORTB2) | (1<<PORTB1) | (1<<PORTB0);
DDRC=(0<<DDC7) | (0<<DDC6) | (0<<DDC5) | (0<<DDC4) | (0<<DDC3) | (0<<DDC2) | (0<<DDC1) | (0<<DDC0);
PORTC=(0<<PORTC7) | (0<<PORTC6) | (0<<PORTC5) | (0<<PORTC4) | (0<<PORTC3) | (0<<PORTC2) | (0<<PORTC1) | (0<<PORTC0);
DDRD=(0<<DDD7) | (0<<DDD6) | (0<<DDD5) | (0<<DDD4) | (0<<DDD3) | (0<<DDD2) | (0<<DDD1) | (0<<DDD0);
PORTD=(0<<PORTD7) | (0<<PORTD6) | (0<<PORTD5) | (0<<PORTD4) | (0<<PORTD3) | (0<<PORTD2) | (0<<PORTD1) | (0<<PORTD0);
// External Interrupt(s) initialization
// INT2: On
// INT2 Mode: Falling Edge (падающий фронт)
GICR|=(0<<INT1) | (0<<INT0) | (1<<INT2);
MCUCR=(0<<ISC11) | (0<<ISC10) | (0<<ISC01) | (0<<ISC00);
MCUCSR=(0<<ISC2);
GIFR=(0<<INTF1) | (0<<INTF0) | (1<<INTF2);
// USART initialization с прарыванием по приёму
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud Rate: 9600
UCSRA=(0<<RXC) | (0<<TXC) | (0<<UDRE) | (0<<FE) | (0<<DOR) | (0<<UPE) | (0<<U2X) | (0<<MPCM);
UCSRB=(1<<RXCIE) | (0<<TXCIE) | (0<<UDRIE) | (1<<RXEN) | (1<<TXEN) | (0<<UCSZ2) | (0<<RXB8) | (0<<TXB8);
UCSRC=(1<<URSEL) | (0<<UMSEL) | (0<<UPM1) | (0<<UPM0) | (0<<USBS) | (1<<UCSZ1) | (1<<UCSZ0) | (0<<UCPOL);
UBRRH=0x00;
UBRRL=0x19;
// SPI disabled
SPCR=(0<<SPIE) | (0<<SPE) | (0<<DORD) | (0<<MSTR) | (0<<CPOL) | (0<<CPHA) | (0<<SPR1) | (0<>8); // старший
//отправляем старший байт
UDR = MSB;
delay_ms(500);
//отправляем младший байт
UDR = LSB;
while (1)
{
}
}
__________________________________________________________________________
С#:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
namespace _1
{
public partial class Form1 : Form
{
byte MSB = 0; //Старший байт
byte LSB = 0; //Младший байт
int MSBLSB = 0; //Результат
bool flag = true;
public Form1()
{
InitializeComponent();
}
// Делегат используется для записи в UI control из потока не-UI
private delegate void LineReceivedEvent(byte размер);
//метод «serialPort1_DataReceived», который будет выполнен в потоке не-UI при поступлении данных в последовательный порт:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
byte размер = (byte)serialPort1.ReadByte();
this.BeginInvoke(new LineReceivedEvent(LineReceived), размер);
}
private void LineReceived(byte размер)
{
if (flag)
{
MSB = размер;
flag = false;
}
else
{
LSB = размер;
flag = true;
MSBLSB = MSB << 8 | LSB; //Склеиваем
label1.Text = Convert.ToString(MSBLSB);
}
}
private void Form1_Load(object sender, EventArgs e)
{
serialPort1.Open();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
serialPort1.Close();
}
}
}
коряво конечно, но работает. подрихтуй пожалуйста. Может кому то пригодится.
очевидно,что в комментарии много лишнего. Извините за мусор. Большое спасибо за сайт. Для решения своей задачи в основном помог ваш материал.
в СVAVR :
unsigned int MSBLSB = 12345 // int с AVR в комп (пример)
unsigned char MSB,LSB;
………….
//режем int на два байта
LSB = (unsigned char)MSBLSB; // младший
MSB = (unsigned char)(MSBLSB>>8); // старший
……………
//отправляем старший байт
UDR = MSB;
delay_ms(500);
//отправляем младший байт
UDR = LSB;
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
в С# :
………….
byte MSB = 0; //Старший байт
byte LSB = 0; //Младший байт
int MSBLSB = 0; //Результат (int с CVAVR)
bool flag = true;
……………
private void LineReceived(byte размер)
{
if (flag)
{
MSB = размер;
flag = false;
}
else
{
LSB = размер;
flag = true;
MSBLSB = MSB << 8 | LSB; //Склеиваем
label1.Text = Convert.ToString(MSBLSB);
}
}
p.s. извиняйте, думаю кому надо найдут что то для себя
в вашем варианте если случайно потеряется байт, то они начнут приходить наоборот, программа перестанет работать
ммда. Буду (м)учиться дальше. Халявы, чувствую, не будет.
может так? :
byte MSB = 0; //Старший байт
byte LSB = 0; //Младший байт
int MSBLSB = 0; //Результат
bool flag_MSB = true;
bool flag_LSB = true;
—
private void LineReceived(byte размер)
{
if (flag_MSB & flag_LSB)
{
MSB = размер;
flag_MSB = false;
}
if (!flag_MSB & flag_LSB)
{
LSB = размер;
flag_MSB = true;
flag_LSB = false;
MSBLSB = MSB << 8 | LSB; //Склеиваем
label1.Text = Convert.ToString(MSBLSB);
}
else
{
MSB = 0;
LSB = 0;
flag_MSB = true;
flag_LSB = true;
MessageBox.Show("ошибка в получении intа");
}
чушь написал. извините. больше не буду докучать.
сделать нормальный протокол который бы стабильно работал, чуть сложнее чем кинуть байт по уарту. Нужны старт стопой байты, таймауты, контрольная сумма и прочие прелести, частично начинал расписывать тут http://avr-start.ru/?p=2230
С передачей в AVR все понятно, а как не передавать данные с windows Form, а считывать переменную с AVR? Передачу я делаю как написано в уроке №2,а как реализовать прием в Visual basic?
Есть переменная Tizm, измеренная температура меняется каждые 0,7сек (18b20), перевожу в значение от 0 до 54 (влазит в 1 байт), и мне это значение нужно передавать в виндовс форм, там я буду строить график.
я на atmega128 использую форматированный вывод printf(«%d»,sek); он выводит только на USART0,как выводить на USART1 также написав например printf1(«%d»,sek);и printf0(«%d»,sek); понятно что надо переписать библиотеку stdio.lib но не догоняю
Ришат, я не очень понимаю зачем использовать printf для вывода на порт AVR (printf печатает строку на стандартный поток вывода, STDOUT), но если вкратце, то есть как минимум два способа:
1. перенаправить поток файла (в вашем случае — порта O_o) на STDOUT с помощью freopen(filename, fileMode, stdout); тогда все операции printf будут выводить данные не в STDOUT
2. использовать fwrite(dataPointer, elementSize, elementCount, stream) — так не придется перенаправлять потоки по нескольку раз
Подозреваю что под капотом у printf много магии происходит, так что не берусь судить, но из любопытства: почему / зачем выводить данные в порт именно им?
если у вас беззнаковое число (не может быть отрицательным) и оно помещается в один байт, то проще попросту записать его напрямую в регистр данных UART, насколько я понимаю: UDR = sek;
ну или используя avr/io :
UART_Send_Char(‘0’ + sek); // сработает только для чисел между 0 и 9
или чуть сложнее (но потенциально быстрее, так как не используется никаких операций форматирования строк):
unsigned int data = sek;
while (data > 0) {
UART_Send_Char(‘0’ + (data % 10));
data /= 10;
}
Ришат, я не очень понимаю зачем использовать printf для вывода на порт AVR (printf печатает строку на стандартный поток вывода, STDOUT), но если вкратце, то есть как минимум два способа:
1. перенаправить поток файла (в вашем случае — порта O_o) на STDOUT с помощью freopen(filename, fileMode, stdout); тогда все операции printf будут выводить данные не в STDOUT
2. использовать fwrite(dataPointer, elementSize, elementCount, stream) — так не придется перенаправлять потоки по нескольку раз
Подозреваю что под капотом у printf много магии происходит, так что не берусь судить, но из любопытства: почему / зачем выводить данные в порт именно им?
Если у вас беззнаковое число (не может быть отрицательным) и оно помещается в один байт, то проще попросту записать его напрямую в регистр данных UART, насколько я понимаю:
UDR = sek;
Или используя avr/io :
UART_Send_Char(‘0’ + sek); // сработает только для чисел между 0 и 9, посему см. ниже
// потенциально быстрее вашего решения, так как не используется никаких операций форматирования строк
unsigned int data = sek; // копируем данные чтобы не переписать значение в sek
while (data > 0) {
UART_Send_Char(‘0’ + (data % 10)); // берем последнюю цифру числа и переводим ее в ASCII-код простым сложением
data /= 10; // отбрасываем из данных уже обработанную цифру
}
NON GENUINE DEVICE FOUND! почему оба устройства это выдают? кварц 16м. мега8. по нажатию любой кнопки на форме меге прилетает два символа из этой надписи. что это такое и как это победить?
в обратном направлении терминалка выдаёт тоже самое. причём неважно что передавать.
гугл первую же строчку выдает, что ваш ftdi чип поддельный.
канал работает?
А как подключить atmega к пк по uart? Есть схема?