AVR Lab устройства на микроконтроллерах AVR

Конвертер уровней RS232-TTL на ST232 для связи микроконтроллера с компьютером

При отладке самодельного GPS приемника приходилось выводить данные из пакетов GGA и VTG на ЖКИ, что при максимальном размере пакета в 80 символов не очень удоб

AVR GPS приемник на микроконтроллере ATmega8

GPS навигация все более и более проникает в массы, я не стал в этом исключением. Два года назад купил себе GPS модуль Lassen iQ от Trimble и активную антенну для него, хотел поиграться с космическими технологиями, но не вышло.GPS навигация все более и более проникает в массы, я не стал в этом исключением. Два года назад купил себе GPS модуль Lassen iQ от Trimble и активную антенну для него, хотел поиграться с космическими технологиями, но не вышло. На самом модуле был нестандартный и неудобный для соединения разъем с шагом 1,27мм, также питание было 3.3В, а у меня были макетки только для 5В питания. 3.3В было достигнуто путем подключения нескольких диодов, на которых гасилось напряжение.

В общем, мне тогда не удалось выжать никаких данных с модуля и я подумал что он паленый. Недели две назад все-таки достал его из загашника, подключил к логическому анализатору. На дисплее компьютера увидел, что модуль очень даже живой и исправно выдает данные. Обрадовавшись этому, решил сделать простенький GPS приемник на ATmega8 с выводом данных на символьный ЖКИ. Вот так выглядит GPS модуль Lassen iQ:
фото GPS приемника
фото GPS приемника
А так выглядит внешняя активная антенна ANT GPS 66801-00 HFL 8CM для него:
фото антенны GPS приемника
Теперь на пальцах про систему GPS. Система GPS представляет собой сеть спутников околоземной орбите, каждый спутник посылает на землю сигнал о своем местоположении и точном времени отправления сигнала, GPS приемник получает эти сигналы и по задержкам сигналов от спутников определяет свое положение. Для этого ему нужно минимум 3 сигнала с разных спутников. Для точного определения координат и высоты над уровнем моря уже нужно 4 спутника. Далее эти сигналы декодируются в координаты, которые посылаются в формате NMEA (в некоторых приемниках есть еще собственный формат вывода координат) к обрабатывающему микроконтроллеру, который выводит эти данные на дисплей. Учитывая прошлые проблемы с подсоединением GPS модуля и его питанием, решил вытравить для него специальную макетную плату, где будет размещаться сам модуль и стабилизатор LP2980-3.3 на 3.3В с обвязкой. Также для согласования уровней, на входа GPS модуля были подключены к делителям напряжения. Вот так это выглядит по завершению:
фото макетки GPS приемника
Далее макетка была присоединена к микроконтроллеру таким макаром:

Тактироваться микроконтроллер будет от внутреннего RC генератора на 4Мгц. Подтягивающие резисторы R1,R4 – 4,7кОм. R2 – ограничительный на 1кОм, R3 - на 17 Ом. Потенциометр VR1 – на 10кОм. Конденсаторы С7,С4 – электролиты на 200мкФ и 100мкФ соответственно. С4,С6,С9 – керамика на 0,1мкФ, С10 – керамика на 1мкФ, С8 – танталовый на 68мкФ.
Как видно из схемы, GPS приемник мы будем только слушать, никак не настраивая его при включении. Для этого выводы RxA, RxB подключим подтягивающими резисторами к питающему напряжению, чтобы приемник случайно не словил каких наводок и не переконфигурировался. Это важно. Теперь разберемся с форматом NMEA. Наш GPS модуль с заводскими настройками, как гласит родной даташит, на выходе TхB каждую секунду генерирует GGA и VTG сообщение, которое можно принять, настроив UART приемник на скорость 4800бит/с 8 бит данных, без проверок. Про формат GGA и VTG сообщений можно прочитать в спецификации стандарта NMEA. Оговорюсь тем, что в каждом сообщении NMEA предусмотрена контрольная сумма, обычный XOR всех байтов, не включая первый символы “$” и последний перед полем контрольной суммы “*”. Но с проверкой контрольной суммы у меня что-то не сложилось, примерно в каждом третьем пакете она не совпадает, хотя провода короткие и больших токов в схеме не проходит. Может это связано с тем, что уровни несогласованны, микроконтроллеру нужно 5В TTL, а приемник выдает 3.3В TTL. Поэтому в прошивке проверка контрольной суммы предусмотрена, но закомментирована. Как выглядит часть GGA пакета на дисплее моего логического анализатора:

Далее это сообщение расшифровывает микроконтроллер и выводит данные на экран. Расшифровать его совсем не сложно, оно представляет собой набор полей, разграниченных запятыми. Поля представлены в ASCII формате, так что заморачиваться с выводом не придется, просто нужно вывести на дисплей нужные поля. Так как на мой дисплей одновременно не помещались все данные, то была подсоединена кнопка к PB0, которая будет определять выводимые данные на дисплей. Теперь рассмотрим собственно код прошивки:

  1. #include <avr/io.h>
  2. #include <avr/interrupt.h>
  3.  
  4. #define RS 2 //RS=PD1
  5. #define E 3 //E=PD3
  6.  
  7. #define TIME 10 //Time constant for LCD delay
  8. //Tick with 4Mhz
  9.  
  10. unsigned char mode=0;
  11.  
  12. unsigned char gga_count=0;
  13. unsigned char vtg_count=0;
  14.  
  15. unsigned char gga_data[90]={};
  16. unsigned char vtg_data[90]={};
  17.  
  18. void pause (unsigned int a)
  19. {
  20. unsigned int i;
  21. for (i=a;i>0;i--);
  22. }
  23.  
  24. void lcd_com (unsigned char lcd)//Write command to LCD
  25. {
  26. unsigned char temp;
  27. temp=(lcd&~(1<<RS))|(1<<E); //RS=0 - it's command
  28. PORTD=temp; //Write higth tetrade, RS, E to portA
  29. asm("nop"); //Settle pause
  30. PORTD=temp&~(1<<E); //Write higth tetrade to LCD
  31. temp=((lcd*16)&~(1<<RS))|(1<<E); //RS=0 - it's command
  32. PORTD=temp; //Write low tetrade, RS, E to portA
  33. asm("nop"); //Settle pause
  34. PORTD=temp&~(1<<E); //Write low tetrade to LCD
  35. pause (10*TIME); //Pause for executing command
  36. }
  37.  
  38. void lcd_dat (unsigned char lcd)//Write data to LCD
  39. {
  40. unsigned char temp;
  41. temp=(lcd|(1<<RS))|(1<<E); //RS=1 - it's data
  42. PORTD=temp; //Write higth tetrade, RS, E to portA
  43. asm("nop"); //Settle pause
  44. PORTD=temp&~(1<<E); //Write higth tetrade to LCD
  45. temp=((lcd*16)|(1<<RS))|(1<<E); //RS=1 - it's data
  46. PORTD=temp; //Write low tetrade, RS, E to portA
  47. asm("nop"); //Settle pause
  48. PORTD=temp&~(1<<E); //Write low tetrade to LCD
  49. pause(TIME); //Pause for loading data
  50. }
  51.  
  52. void lcd_init (void) //Init LCD
  53. {
  54. lcd_com(0x2c); //4-wire data bus, 1-row, 5x8 symbol size
  55. pause(100*TIME);
  56. lcd_com(0x0c); //Show image, cursor show
  57. pause(1000*TIME);
  58. lcd_com(0x01); //Clear screen and set cursor on 0x00 position
  59. pause (100*TIME);
  60. }
  61.  
  62. void uart_init(void)
  63. {
  64. UBRRH = 0x00; //Set 4800 bit per second
  65. UBRRL = 0x33;
  66. UCSRA = 0x00;
  67. UCSRB = (1<<RXCIE)|(1<<RXEN)|(0<<TXEN);
  68. //Receive and transmit enable, interrupt on receive complete enabled
  69. UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);
  70. }
  71.  
  72. unsigned char search_gga_field (unsigned char x)//Search x-th field in GGA packet
  73. {
  74. unsigned char i, c;
  75. c=0;
  76. for (i=0;i<gga_count;i++)
  77. {
  78. if (gga_data[i]==',') c++;
  79. if (c==x) return i+1;
  80. }
  81. return 0;
  82. }
  83.  
  84. //Search x-th field in VTG packet
  85. unsigned char search_vtg_field (unsigned char x)
  86. {
  87. unsigned char i, c;
  88. c=0;
  89. for (i=0;i<vtg_count;i++)
  90. {
  91. if (vtg_data[i]==',') c++;
  92. if (c==x) return i+1;
  93. }
  94. return 0;
  95. }
  96.  
  97. void write_gps_data (unsigned char crc_valid) //Write GPS data on LCD
  98. {
  99. unsigned char i,temp;
  100. if (crc_valid==0) {
  101. //If CRC not confirm
  102. lcd_init ();
  103. lcd_dat('n');
  104. lcd_dat('o');
  105. lcd_dat(' ');
  106. lcd_dat('v');
  107. lcd_dat('a');
  108. lcd_dat('l');
  109. lcd_dat('i');
  110. lcd_dat('d');
  111. return;
  112. }
  113.  
  114. if (gga_data[search_gga_field(6)]=='0')
  115. {//If no GPS signal
  116. lcd_init();
  117. lcd_dat('n'); lcd_dat('o'); lcd_dat(' ');
  118. lcd_dat('s'); lcd_dat('i'); lcd_dat('g');
  119. lcd_dat('n'); lcd_dat('a'); lcd_dat('l');
  120.  
  121. lcd_com(0xc0);
  122. lcd_dat(gga_data[search_gga_field(7)+1]);
  123. lcd_dat(' '); lcd_dat('s'); lcd_dat('a');
  124. lcd_dat('t'); lcd_dat(' '); lcd_dat('a');
  125. lcd_dat('v'); lcd_dat('a'); lcd_dat('i');
  126. lcd_dat('l'); lcd_dat('a'); lcd_dat('b');
  127. lcd_dat('l'); lcd_dat('e');
  128. return;
  129. }
  130. if (mode==0)
  131. {//Write Latitude and Longitude
  132. lcd_init();
  133. lcd_dat('L'); lcd_dat('a'); lcd_dat('t');
  134. lcd_dat(':');
  135. temp=search_gga_field(2);
  136. for (i=temp;i<temp+9;i++)//Write Latitude
  137. {
  138. if ((i-temp)==2) lcd_dat(0xdf);
  139. lcd_dat (gga_data[i]);
  140. }
  141. lcd_dat(0xe7);
  142. lcd_dat (gga_data[search_gga_field(3)]);
  143.  
  144. lcd_com(0xc0);
  145. lcd_dat('L');
  146. lcd_dat('n');
  147. lcd_dat(':');
  148. temp=search_gga_field(4);
  149. for (i=temp;i<temp+10;i++) //Write Longitude
  150. {
  151. if ((i-temp)==3) lcd_dat(0xdf);
  152. lcd_dat (gga_data[i]);
  153. }
  154. lcd_dat (0xe7);
  155. lcd_dat (gga_data[search_gga_field(5)]);
  156. }
  157. if (mode==1) { //Write altitude and speed
  158. lcd_init();
  159. lcd_dat('A'); lcd_dat('l'); lcd_dat('t');
  160. lcd_dat('i'); lcd_dat('t'); lcd_dat('u');
  161. lcd_dat('d'); lcd_dat('e'); lcd_dat(' ');
  162. temp=search_gga_field(9);
  163. while (gga_data[temp]!=',') //Write altitude
  164. {
  165. lcd_dat(gga_data[temp]);
  166. temp++;
  167. }
  168. lcd_dat('m');
  169.  
  170. lcd_com(0xc0);
  171. lcd_dat('S'); lcd_dat('p');
  172. lcd_dat('e'); lcd_dat('e');
  173. lcd_dat('d'); lcd_dat(' ');
  174.  
  175. temp=search_vtg_field(7);
  176. while (vtg_data[temp]!=',') //Write speed
  177. {
  178. lcd_dat(vtg_data[temp]);
  179. temp++;
  180. }
  181. lcd_dat('k'); lcd_dat('m');
  182. lcd_dat('/'); lcd_dat('h');
  183. }
  184. if (mode==2) { //Write UTC time and used satellites
  185. lcd_init();
  186. lcd_dat('U'); lcd_dat('T');
  187. lcd_dat('C'); lcd_dat(' ');
  188.  
  189. temp=search_gga_field(1);
  190. for (i=temp;i<temp+6;i++) //Write UTC
  191. {
  192. if (((i-temp)==2)||((i-temp)==4) ) lcd_dat (':');
  193. lcd_dat(gga_data[i]);
  194. }
  195. lcd_com(0xc0);
  196. lcd_dat('S'); lcd_dat('a'); lcd_dat('t');
  197. lcd_dat('e'); lcd_dat('l'); lcd_dat('l');
  198. lcd_dat('i'); lcd_dat('t'); lcd_dat('e');
  199. lcd_dat('s'); lcd_dat(' ');
  200.  
  201. temp=search_gga_field(7);
  202. lcd_dat(gga_data[temp]); //Write satellites
  203. if (gga_data[temp+1]!=',') lcd_dat(gga_data[temp+1]);
  204. }
  205.  
  206. return;
  207. }
  208.  
  209. unsigned char check_crc (void) //Check CRC16
  210. { /*unsigned char i,gga_crc,vtg_crc;
  211.  
  212.  gga_crc=0x00;
  213.  
  214.  i=1;
  215.  while ((gga_data[i]!='*')&&(i<90))
  216.   {
  217.   gga_crc^=gga_data[i];
  218.   i++;
  219.   }
  220.  i++;
  221.  if (gga_data[i]!=(0x30+(gga_crc>>4))) return 0;
  222.  i++;
  223.  if (gga_data[i]!=(0x30+(gga_crc&0x0f))) return 0;
  224.  
  225.  vtg_crc=0x00;
  226.  
  227.  i=1;
  228.  while ((vtg_data[i]!='*')&&(i<90))
  229.   {
  230.   vtg_crc^=vtg_data[i];
  231.   i++;
  232.   }
  233.  i++;
  234.  if (vtg_data[i]!=(0x30+(vtg_crc>>4))) return 0;
  235.  i++;
  236.  if (vtg_data[i]!=(0x30+(vtg_crc&0x0f))) return 0; */
  237.  
  238. return 1;
  239. }
  240.  
  241. ISR (USART_RXC_vect) //Starts when first byte of NMEA message been received
  242. {
  243. gga_count=0;
  244. gga_data[gga_count]=UDR;//Get first byte to GGA packet
  245. gga_count++;
  246.  
  247. do { //Receive GGA packet
  248.  
  249. while ( !(UCSRA & (1<<RXC)) )
  250. ;
  251. gga_data[gga_count]=UDR;
  252. gga_count++;
  253.  
  254. } while ((gga_data[gga_count-1]!=0x0a)&&(gga_count<90));
  255.  
  256. vtg_count=0; //Receive VTG packet
  257. do {
  258.  
  259. while ( !(UCSRA & (1<<RXC)) )
  260. ;
  261. vtg_data[vtg_count]=UDR;
  262. vtg_count++;
  263.  
  264. } while ((vtg_data[vtg_count-1]!=0x0a)&&(vtg_count<90));
  265.  
  266. write_gps_data(check_crc()); //Write data and check CRC16
  267.  
  268. return;
  269. }
  270.  
  271. int main(void)
  272. {
  273. DDRD=0xfc; //Init PORTD as output
  274.  
  275. DDRB=0x00; //Init PORTB, PB0 as input
  276. PORTB=0x01;
  277.  
  278. pause(5000); //Settle pause
  279. lcd_init(); //Init LCD
  280.  
  281. lcd_dat('n'); lcd_dat('o'); lcd_dat(' ');
  282. lcd_dat('g'); lcd_dat('p'); lcd_dat('s');
  283. lcd_dat(' '); lcd_dat('m'); lcd_dat('o');
  284. lcd_dat('d'); lcd_dat('u'); lcd_dat('l');
  285. lcd_dat('e');
  286.  
  287. uart_init(); //Init UART
  288. sei(); //Interrupt enable
  289. while (1)
  290. if ((PINB&0x01)==0x00) { //If key pressed
  291. while ((PINB&0x01)==0x00) //Wait to unpress
  292. ;
  293. pause(1000); //Pause
  294. mode=(mode+1)%3; //Change mode
  295. }
  296.  
  297. return 1;
  298. }

В начале приема пакета срабатывает прерывание по принятию байта по UART. Далее байты один за одним упаковываются в массивы gga_data vtg_data содержащих соответственно GGA и VTG пакет. Проверяется контрольная сумма (фейково) и выводиться на дислей. Если поле 6 в пакете GGA равно нулю, то GPS приемник не смог определить координаты и собственно пишем «no signal», если же контрольная сумма не совпадает (фейковая) пишем «no valid». Обработка же нажатия кнопки происходит в теле основной программы. Глобальная переменная mode служит для определения данных, которые будут выводиться на ЖКИ (0 – выводится широта и долгота, 1 – выводится высота и скорость, 2 – выводится точное время UTC и количество видимых приемником спутников). Переменные gga_count и vtg_count служат для определения количества принятых байт в GGA и VTG пакете.
Как работает собранный девайс у меня на подоконнике можно просмотреть на видео:

Скачать исходный код в виде проекта для AVR Studio 4 и разводку печатной платы для Sprint layout 5

Семисегментный индикатор и динамическая индикация на AVR микроконтроллере ATmega8

В разных конструкциях бывает оправдано использовать семисегментные светодиодные индикаторы, дешево и сердито по сравнению с символьными ЖКИ. Светодиодный индикатор представляет собой восемь светодиодов (7 для представления цифры и 1 для точки) расположенные в виде слегка наклоненной цифры:
В разных конструкциях бывает оправдано использовать семисегментные светодиодные индикаторы, дешево и сердито по сравнению с символьными ЖКИ. Светодиодный индикатор представляет собой восемь светодиодов (7 для представления цифры и 1 для точки) расположенные в виде слегка наклоненной цифры:
семисегментный светодиодный индикатор
Светодиоды внутри имеют общий анод (ОА) или общий катод (ОК). То есть, для управления одной цифрой нужно 8 выводов микроконтроллера. А что же делать, когда нужно управлять, например, четырьмя цифрами? Использовать микроконтроллер с 4*8=32 выводами? Не экономично.

Для такого случая придумали динамическую индикацию. Для этого соединяем выводы, которые отвечают за включение сегментов в общую шину, а общими анодами (катодами) будем управлять через транзисторы. В отдельный момент времени горит только одна цифра. Таким образом быстро перебирая цифры на дисплее (как кадры в фильме) мы получим эффект постоянно горящего изображения. В замедленном варианте, как это происходит, можно посмотреть на картинке:
анимация динамической индикации
А вот ускоренная в 25 раз картинка, уже начинают вырисовываться контуры «12.34»:
анимация динамической индикации
Используя принцип динамической индикации мы сможем управлять четырьмя цифрами при помощи 8+4=12 выводов. Использование же 2-х сдвиговых регистров HC595 может сократить это число до 4. Рассмотрим схему подключения к микроконтроллеру:

Управлять же индикатором будем с помощью микроконтроллера ATmega8. Резисторы R5-R13 – ограничительные на 470 Ом. R1-R4 – по 1кОм. Транзисторы Q1-Q4 – любые PNP типа, я использовал BC807 в планарном исполнении. Конденсаторы С5,С7 – электролиты по 100 и 200мкф соответственно, С4,С6 – керамика по 0,1мкф. Так как индикатор с общим анодом, то соответственно включение разряда/сегмента производиться низким уровнем.

Для индикаторов с общим катодом схема аналогична, только транзисторы следует взять NPN структуры, и управляться индикатор будет высоким уровнем.
Продемонстрируем сказанное, напишем программу, которая будет перебирать числа от 0 до 9999 и выводить их на семисегментный индикатор.

  1. #include <avr/io.h>
  2. #include <avr/interrupt.h>
  3. // 0 1 2 3 4 5 6 7 8 9
  4. const unsigned char codes[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
  5.  
  6. unsigned char data[4]={0x00,0x00,0x00,0x00};
  7. unsigned char counter=0;
  8.  
  9. void pause (unsigned int a)
  10. { unsigned int i; for (i=a;i>0;i--); }
  11.  
  12. void init_timer (void)
  13. { TIMSK=(1<<TOIE0); //Enable timer overflow interrupt
  14. TCCR0=(0<<CS00)|(1<<CS01)|(0<<CS02); //Prescaller = /1
  15. }
  16.  
  17. void convert_data (unsigned int x)
  18. {unsigned int temp,res;
  19. temp=x;
  20. res=temp/1000; //Calculate 1000-s
  21. data[3]=codes[res];
  22. temp=temp-res*1000;
  23.  
  24. res=temp/100; //Calculate 100-s
  25. data[2]=codes[res];
  26. temp=temp-res*100;
  27.  
  28. res=temp/10; //Calculaate 10-s
  29. data[1]=codes[res];
  30. temp=temp-res*10;
  31.  
  32. data[0]=codes[temp]; //Calculate 1-s
  33. }
  34.  
  35. ISR (TIMER0_OVF_vect)
  36. {PORTD=0xff;
  37. PORTB=~_BV(counter); //Enable digit
  38. PORTD=~data[counter]; //Write code
  39. counter=(counter+1)%4; //Increment digit counter
  40.  
  41. TCNT0=0x00; //Clear timer
  42. }
  43.  
  44. int main(void)
  45. { unsigned int x=0;
  46. DDRD=0xff;
  47. PORTD=0x00;
  48. DDRB=0x0f;
  49. PORTB=0x0f;
  50.  
  51. pause(1000); //Settle pause
  52. init_timer(); //Init timer
  53. sei(); //Interrupt enable
  54. while(1)
  55. {convert_data(x); //Conver data to codes
  56. if (x<9999) x=x+1; //Increment data
  57. else x=0;
  58. pause(30000);
  59. }
  60. return 1;
  61. }

Код очень простой. В массиве codes находятся коды, которые следует выводить на порт, чтобы получить желаемую цифру. Смена активной цифры производиться по прерыванию от переполнения таймера 0. А функция convert_data(int x) раскладывает число х по разрядам, и записывает соответствующие коды в массив data, данные из которого выводятся непосредственно на индикатор, при срабатывание прерывания.
Что из этого вышло, можно глянуть на рисунках:
фото динамической индикации
фото динамической индикации

Исходный код можно скачать в виде проекта под AVR Studio 4
Также, может кому пригодиться, платка для моего табло в формате .lay для программы Sprint Layout 5

Шина 1-wire, пример термометра на DS18B20 и микроконтроллере AVR, WH1602, ЖКИ

Шина 1-Wire была запатентована компанией Dallas Semiconductor, и была призвана наладить полудуплексную связь всего по одному сигнальному проводу. Также возможны варианты использования паразитного питания по линии данных (потому что во все микросхемы 1-Wire встроен конденсатор номиналом 800пФ).

Шина I2C и AVR пример, PCF8583 (RTC) вечный календарь

Шина I2C очень распространена, существуют АЦП, ЦАП, термометры, память с интерфейсом I2C. При проектировании устройств и их починке приходится сталкиваться с этой шиной. Рассмотрим ее поподробнее.

RSS-материал