AVR Урок 29. SPI. Внешний АЦП MCP 3201

 

 

 

     

    Урок 29

 SPI. Внешний АЦП MCP 3201

 

 

Продолжаем цикл уроков по шине SPI. И сегодня мы впервые поработаем с данной шиной на приём данных от устройства. Я думаю этого уже многие ждут с нетерпением.

Для этого мы возьмём микросхему MCP3201. Эта микросхема является внешним аналого-цифровым преобразователем. Разработчиком данной микросхемы является компания Microchip Technology.

Данная микросхема не такая крутая, как может показаться на первый взгляд, скорость у неё не очень велика, но сигнал даже динамический в пределах 100 кГц она отследить может.

Но нам этого и не нужно. Нам данный внешний АЦП поможет получше закрепить наши навыки по работе и программировании шины SPI и заодно пообщаться по данной шине на приём, чего мы никак не дождёмся.

Давайте изучим для начала основные технические характеристики данного преобразоавтеля.

Частота тактирования по шине SPI может достигать 1,6 МГц при условии, что питаться микросхема будет от 5 вольт, а если она будет питаться от 2,7 вольт — то 0,8 МГц.

Если нам будут нужны какие-то ещё характеристики, то мы к ним вернёмся в процессе написания кода.

Данные от данной микросхемы принимаются с выхода Dout

 

Image00

 

И приниматься данные будут на входе MISO нашего контроллера.

Как только мы запустим тактовые импульсы и опустим шину выбора, данные — начнут передаваться в виде двух байтов, один старший, другой младший, так как АЦП у нас 12 битный и вся информация в рамках одного байта не уместится. И формат такой: в старшем байте идут пустые первые три бита, а в младшем пустой будет последний младший бит.

Вот распиновка нашей микросхемы

 

Image01

 

Здесь мы видим, что данная микросхема АЦП является 8-контактной, существует в корпусе DIP, а также в других различных корпусах. Мне, например, в руки данная микросхема попала в корпусе SOIC, поэтому пришлось применить недорогой и незатейливый переходничок, который мы увидим несколько позже, и сделать что-то наподобие DIP, чтобы воткнуть её в макетную плату, то есть получилось что-то наподобие маленькой табуретки.

Теперь ножки

Vref — это контакт для подачи опорного напряжения. То есть, подадим мы, например сюда пять вольт — и это будет наше максимальное напряжение.

In+ — это у нас аналоговый вход. На нём мы и измеряем наше напряжение.

In- — это вход для регулировки АЦП. То есть, если существует какая-то погрешность, то мы сюда подаём корректирующее напряжение от -100 миливольт до +100 миливольт.

Vss — общий провод.

CS/SHDN — ножка для выбора и отключения микросхемы.

Dout — ножка цифровых данных измеренного напряжения.

CLK — ножка синхронизации или тактирования.

Vdd — ножка питания от 2,7 вольт до 5,5 вольт.

 

Вот так вот рассчитывается цена градаций напряжения

 

Image02

 

А вот так само наряжение, вернее наоборот, по измеряемому и опорному напряжению расчитывается информационная величина, которую мы получим из шины

 

Image03

 

То есть, чтобы расчитать напряжение, то нам соответственно нужно будет его выразить из формулы и применить необходимый расчёт в коде. Для этого мы научимся включать плавающую точку в настройках Atmel Studio, так как по умолчанию она у микроконтроллера отключена для экономии энергии.

Также микросхема поддерживает два режима передачи данных по SPI — режим 0:0 и режим 1:1. Данные режимы мы изучали в уроке по знакомству с шиной SPI.

 

Первый режим (нажмите на картинку для увеличения изображения)

 

Image04_0500

 

Второй режим (нажмите на картинку для увеличения изображения)

 

Image05_0500

 

Также здесь мы и видим, как именно у нас передаются полезные биты информации.

Ну, можно сказать, с микросхемой мы немного познакомились.

Теперь проект.

Проект я по славной традиции взял из прошлого занятия, а назвал его по имени нашей микросхемы ADC3201.

Из главного файла я удалил всё ненужное, оставил только функции SPI_init, SPI_SendByte и main.

Также была удалена библиотека led и подключена библиотека для символьного дисплея lcd.

Поэтому файл main.h приобрёл теперь вот такой вид

 

#ifndef MAIN_H_

#define MAIN_H_

#define F_CPU 8000000UL

#include <avr/io.h>

#include <avr/interrupt.h>

#include <util/delay.h>

#include <stdio.h>

#include <stdlib.h>

//—————————————

#include "lcd.h"

#endif /* MAIN_H_ */

 

Библиотека LCD, состоящая из файлов lcd.h и lcd.c, была взята из проекта одного из прошлых уроков.

Функия main() после очистки приняла следующий вид

 

int main(void)

{

  SPI_init();

  while(1)

  {

  }

}

 

Для начала инициализации дисплея сначала настроим для него ножки портов. добавив функцию в главный модуль

 

#include "main.h"

void port_ini(void)

{

  PORTD=0x00;

  DDRD=0xFF;

}

 

И также вызовем её в main()

 

int main(void)

{

  port_ini(); //Инициализируем порты

 

Ну и для полной инициализации дисплея мы вызовем функцию его инициализации и заодно очистим его

 

port_ini(); //Инициализируем порты

LCD_ini(); //Инициализируем дисплей

clearlcd(); //Очистим дисплей

SPI_init();

 

Ну, а в бесконечном цикле мы пока установим дисплей на начало координат и применим задержку

 

while(1)

{

  setpos(0,0);

  _delay_ms(500);

}

 

Ну и теперь, прежде чем писать дальнейший код, нам надо ознакомиться со схемой подключения внешнего АЦП к контроллеру. Для этого пока посмотрим её в протеусе (нажмите на картинку для увеличения изображения)

 

Image06_0500

 

Всё довольно-таки просто. используется у нас уже шина MISO, остальное собственно без изменений. Для иммитации изменения напряжения мы собрали небольшой делител, состоящий из одного переменного резистора.

Ну теперь нам как-то нужно попросить у нашей микросхемы байт. А попросим мы его следующим образом. Мы напишем универсальную функцию обмена байтами по кругу через шину SPI, хотя наша микросхема подключена только к одному информационному проводу контроллера, но это не страшно. Функция от этого никак не пострадает. Для этого мы скопируем функцию приёма байта и немного её подправим

 

unsigned char SPI_ChangeByte (char byte)

{

  SPDR = byte;

  while(!(SPSR & (1<<SPIF)));//подождем пока данные передадутся (обменяются)

  return SPDR;

}

 

Как мы видим, функция-то не очень изменилась. Мы всего лишь после окончания приёма-передачи считываем регистр данных и возвращаем его. Но так как у нас по схеме с контроллера ничего не передаётся, но в силу технологии устройства SPI нам ведомое устройство тем не менее байт отдаст, оно никуда не денется.

 

 

Теперь нам нужно вышенаписанную функцию как-то использовать и всё-таки запросить что-то. Для этого напишем ещё одну функцию ниже. Данная функция будет принимать байты, обрабатывать их и возвращать нам уже величину, очищенную от ненужных битов

 

unsigned int Read_3201 (void)

{

}

 

В данной функции мы объявим две переменных для двух байтов, затем мы опустим шину выбора микросхемы, так как активный уровень у нас низкий, тем самым мы заставим микросхему начинать отправку байтов

unsigned int Read_3201 (void)

{

  unsigned int b1,b2;

  PORTB &= ~(1<<PORTB2); //низкий уровень

 

Считаем два байта из микросхемы, воспользовавшись вышенаписанной функцией

 

PORTB &= ~(1<<PORTB2); //низкий уровень

b1=SPI_ChangeByte(0);//первый байт

b2=SPI_ChangeByte(0);//второй байт

 

Соберём наши два байта в единую величину и уберём ненужный мусор

 

b2=SPI_ChangeByte(0);//второй байт

b1=(b1<<8)|b2;//собираем два байта в двухбайтовую величину

b1<<=3;//убираем ненужные биты (3 слева и 1 справа

b1>>=4;

 

Затем поднимем шину выбора, заставив микросхему перестать нам отправлять данные, пусть немного отдохнёт, и возвратим нашу величину из функции

 

  b1>>=4;

  PORTB |= (1<<PORTB2); //высокий уровень

  return b1;

}

 

Ну, и теперь мы добавим ещё одну функцию ниже, которая преобразует нам нашу сырую двухбайтовую величину в результат с плавающей точкой, который будет в себе уже нести измеряемое напряжение

 

float Convert_3201 (unsigned int dt)

{

  float dt1;

  dt1=((float)dt*5)/4096;

  return dt1;

}

 

Тут также всё просто. мы, применяя явное преобразование типов умножаем нашу величину на 5, так как это опорное напряжение, и разделим на 4096 согласно разрешающей способности нашего внешнего АЦП.

Функция-то у нас написана. Но чтобы она коректно и быстро работала, нам неоходимо включить работу с плавающей точкой средствами Atmel Studio.

Для этого идём по меню

 

Project->ACD3201 Properties

 

Image07

 

Затем в открывшемся настроечном окне слева выбираем AVR/GNU Linker -> Miscellaneous и в единственной строке редактирования пишем -Wl,-u,vfprintf -lprintf_flt -lm

 

Image08

 

Теперь, когда мы всё настроили и написали все служебные функции, идём в функцию main() в бесконечный цикл и попробуем, вызвав функцию конвертации, отобразить на дисплее наше измеряемое напряжение.

 

Добавим сначала локальную переменную для временного хранения нашей величины с плавающей точкой

 

int main(void)

{

  float dt=0;

 

Сначала получим напряжение от внешнего АЦП

 

setpos(0,0);

dt=Convert_3201(Read_3201());

 

И, наконец-то настал час использования функции sprinf, давно обещанной, так как с посимвольным выводом числовых величин мы уже вдоволь наигрались.

Но прежде, чем данную функцию использовать, добавим в main() локальную переменную для массива символов или переменную строки ограниченной длины

 

float dt=0;

char str[10];

 

Ну и теперь вызовем функцию sprinf в бесконечном цикле, которая нам преобразует нашу величину с плавающей точкой в строку

 

dt=Convert_3201(Read_3201());

sprintf(str,"%.2fv ",dt);

 

Вторым входным параметром функции является строка, в которой находится только лишь строка форматирования и пробел. Данная строка форматирования означает, что бы будем преобразовывать именно величину с плавающей точкой, причём после точки мы будем использовать две цифры.

В дальнейшем мы теперь постоянно будем обращаться к функции sprintf, которая значительно облегчает код преобразования различных величин в строку.

Дальше мы отобразим нашу полученную в результате преобразования с помощью функции sprintf строку на дисплее

 

  sprintf(str,"%.2fv ",dt);

  str_lcd(str);

  _delay_ms(500);

}

 

Теперь проверим работу кода в протеусе, перед этим собрав код

 

Image09

 

Прежде чем прошить контроллер, посмотрим практическую схему подключения

 

Image10

 

Мы видим нашу микросхемку на отладочной плате, вставленную в макетную плату благодаря нехитрому переходнику.

Теперь мы можем прошить контроллер и посмотреть результат уже на живом дисплее, для этого для проверки точности нашего АЦП мы к измеряемому напряжению ещё подключим хороший мультиметр

 

Image11

 

Мы видим, что с помощью внешнего АЦП мы измерили напряжение 4,96, а прибор показывает чуть меньше — 4,91.

Скорей всего это не из-за того, что у нас неточный АЦП, а из-за того, что у нас вся наша схема питается напряжением не ровно 5 вольт.

Можно его измерить и выяснить. Оно у нас именно 4,95 вольт.

Поэтому в функции конвертации мы должны умножать на 4,95, а не на 5. Исправим это

 

dt1=((float)dt*(4.95))/4096;

 

Скобки здесь не обязательны, но с ними как-то поспокойнее.

Проверим результат, собрав код и прошив контроллер. У нас все равно показание не совсем равны, поэтому. поиграв с умножаемой величиной, можно всё таки добиться точного результата. Возможно дело ещё в том что, напряжение нестабильно на всех ножках из-за сопротивления проводов.

Вообщем, если подать точное стабильное опорное напряжение, то можно добиться отчень точных результатов с такой разрешающей способностью.

Но дело даже не в этом. Дело в том, что сегодня мы лишний раз поигрались с шиной SPI и смогли добиться результатов уже исользуя её для приёма данных.

А на следующем занятии мы подключим дисплей к другому контроллеру, чтобы его впоследствии по шине SPI подключить к нашей отладочной плате с контроллером ATmega8.

 

  

    Предыдущий урок Программирование МК AVR Следующий урок

     

    Исходный код

 

 

Техническая документация на микросхему MCP3201

     

Программатор, микросхему MCP3201 и дисплей можно приобрести здесь:

Программатор (продавец надёжный) USBASP USBISP 2.0

Микросхема АЦП 12-разрядный MCP3201 — 10 шт

Дисплей LCD 16×2

     

     

    Смотреть ВИДЕОУРОК (нажмите на картинку)

     

    AVR SPI. Внешний АЦП MCP 3201

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*