Урок 88
Часть 1
SD. SPI. FATFS
Продолжаем работать с народной отладочной платой STM32F103RCT6, с которой мы уже давно занимаемся и которая. судя по результатам наших занятий, показала себя с очень хорошей стороны. Данная плата и установленный на ней контроллер хоть и не все прелести достижений прогресса в себе несут, но очень многие, что при такой цене очень неплохо.
Также я думаю, что сейчас мы подошли к такому моменту, что никакой памяти на борту данного контроллера вскоре может не хватить для хранения наших постоянно растущих объёмов данных, поэтому пора бы подключать уже что-то такое, что позволит нам хранить файлы немалого объёма. Я сначала склонялся к USB, который также присутствует на борту данной платы, но, потестировав его плотно, понял, что он мягко говоря не совсем уверенно работает.
Тогда заключительный мой выбор пал на карту SD. Только едиснтвенная проблема заключается в том, что интерфейса SDIO, который так любят данные карты, на нашем контроллере нет. Но это меня все равно не остановило и я вспомнил, что карты SD также неплохо работают и с использованием интерфейса SPI, хотя некоторый функционал при использовании данной шины становится недоступным, и скорость передачи данных в этом случае гораздо ниже, но это меня также не остановило. Причем на многих форумах, я увидел, что многие испытали проблемы с подключением по SPI данных карт к контроллерам STM, особенно с использованием библиотечных функций HAL, начали изобретать какие-то программные дрыгания ногами, что естественно вносит некорректную работу всей программы (аппаратное всегда лучше). И все эти кривотолки меня ещё больше подтолкнули на развеивание всех этих мифов и поэтому я всё-таки решился на данный урок. Да и просьб по этому вопросу было огромное количество, потому что, я думаю, таких плат на руках немало.
Также перед нами встанет ещё одна задача. Подключить библиотеку FatFS, чтобы мы могли работать с файловой системой, а не с блоками и секторами.
Что ж, начнём!
В уроке 44 мы уже подключали такую карту по интерфейсу SDIO к другому контроллеру, но тем не менее посмотреть его вам будет полезно, так как мы там также пользовались библиотекой FatFS. Но это позже. Пока до библиотеки далеко. Сейчас нам главное научиться работать с картой по интерфейсу SPI, а для этого не менее полезно будет посмотреть урок 33 по AVR, в котором мы также используем SPI в качестве шины для подключения карты SD. Просто там мы согласовывали все свои действия с даташитом и не хотелось бы заново одно и то же постить многократно. Тем не менее это не освобождает нас от чтения документации, чтобы в дальнейшем не столкнуться с теми же граблями, с которыми пришлось столкнуться авторам вопросов на форумах. Вся документация размещена на официальном сайте https://www.sdcard.org/downloads/pls/ .
Начнём со схемы. Точно так же как и в предыдущих уроках по подключению модуля LAN, питать мы наш контроллер будет от стабилизированного DC-DC-преобразвателя напряжением 3,3 вольта, программировать будем с помощью программатора ST-Link самого дешёвого, а питать мы от него нашу плату не будем. Модуль с картой Micro-SD мы будем питать также от преобразователя.
Модулей с картоприёмником Micro-SD у меня целых 3. Один из них самодельный, а два я заказывал в интернет-магазине.
Вот мои модули (нажмите на картинку для увеличения изображения):
a) это серийный модуль с aliexpress в том виде, как он приехал;
б) это такой же модуль, только здесь я отпаял (сдул) все подтягивающие резисторы.
в) самодельный модуль, который использовался в уроке 44.
Думаю, что остановимся мы на варианте b, в котором нет подтягивающих резисторов, без них всё прекрасно работает на скорости 10 мегабит в секунду, я пробовал.
Также присланный модуль не просто красивей, но на нём есть подписи каждой ножки, какую она выполняет роль. Хотя на рисунке мы видим всё это для режима SDIO, но давайте попробуем перевернуть модуль
И мы увидим, что здесь уже присутствуют надписи для режима SPI, что несомненно очень удобно.
Сразу скажу, что прежде чем дать данный урок, я повозился с откликом карты, поизучал всё с логическим анализатором и хочу этим процессом также немного с вами поделиться. Поэтому мы подключим данный модуль к контроллеру не обычными проводами типа мама-мама, а трёхконечными, которые я спаял сам, а место спайки оформил в термоусадку
А вот логический анализатор, который есть у меня в наличии. Простейший, наверно самый дешёвый, но мне очень помогает
Подключим провода сначала к модулю карты MicroSD и к логическому анализатору
Затем подведём от контроллера общий провод к анализатору, а также подведём питание к модулю карты, подключим питание, подключим логический анализатор к ПК с помощью провода Mini-USB, а также подключим все провода шины SPI к отладочной плате. Подключать мы будем к шине SPI2, так как SPI1 у нас в последствии будет занята модулем LAN. Возможно, впоследствии мы подключим всё от одной шины и, думаю, всё это будет работать, причём и скорость у нас та же, да и провода трёхконечные у нас уже имеются, не всё же время нам с подключенным логическим анализатором работать. Но пока будет именно SPI2. Заодно мы её проверим тоже на работоспособность. А также подключим ещё переходник USART-USB, чтобы мы могли отслеживть какую-то информацию. Когда-то мы это делали с помощью дисплея, но USART, как подтвердила практика, оказался удобнее. И информации больше, и подключать легче. В результате получится вот такая схема (нажмите на картинку для увеличения изображения)
Теперь, я думаю, самое время перейти к проекту. Проект целесообразнее всего создать новый. Запустим для этого Cube MX, выберем наш контроллер, подключим сначала керамический резонатор, а также включим шину программирования программирования Serial Wire
Включим SPI2
Включим 2-й таймер
Также включим USART1
Включим FatFS
Включим ножку PC13, отвечающую за светодиод на выход
Ещё на выход включим ножку PA3, которая будет выполнять роль ножки Chip Select для шины SPI
Переходим в раздел Clock Configuration по соответствующей вкладке и настроим следующие параметры (нажмите на картинку для увеличения изображения)
Идём по следующей вкладке в раздел Configuration. Сначала настроим USART
Включим прерывания в USART
Сохраним настройки USART, теперь настроим SPI
Также сохраним настройки. Теперь таймер, который мы настроим на частоту 100 килогерц, чтобы прерывания происходили раз в 10 милисекунд
Прерывания тоже включаем
Также настроим FATFS, включив поддержку большого сектора и длинных имён файлов. Кодовую страницу каждый подберёт под себя. Только учтите, что не для всех кодовых страниц файлы с массивами присутствуют в поставляемой библиотеке FATFS. Тогда либо пользоваться существующими страницами, либо искать файлы, либо писать самим массивы. Мы же оставим кодовую страницу по умолчанию
Теперь настроим ножки GPIO. Начнём с ножки PC13. Увеличим немного скорость
То же самое проделаем и с ножкой PA3
Теперь зайдём в настройки, придумаем имя для проекта, а также выберем желаемую среду программирования
Сохраним настройки, сгенерируем проект, откроем его в Keil, настроим программатор на автоперезагрузку и попробуем собрать проект.
У нас будут две ошибки, сами увидите какие. Ошибки будут потому, что мы включили поддержку длинных имён и проектогенератор Cube по идее должен был подключить файл с функциями, на которые ругается линкер, но по каким-то причинам он этого не сделал. Поможем ему! Думаю, он «не забудет» нашу доброту.
Подключим файл ccsbcs.c из папки «Наш проект/Middlewares\Third_Party\FatFs\src\option» в раздел дерева проекта «Middlewares/FatFs«
Запустим таймер в функции main()
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);
Создадим в файле main.c глобальную переменную для таймера-счётчика
/* Private variables ---------------------------------------------------------*/
volatile uint16_t Timer1=0;
Также создадим обработчик прерывания, в котором будем постоянно инкрементировать данную переменную
/* USER CODE BEGIN 4 */
//----------------------------------------------------------
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==&htim2)
{
Timer1++;
}
}
//----------------------------------------------------------
/* USER CODE END 4 */
Создадим два файла — sd.c и sd.h для работы с картой SD следующего содержания
sd.h
#ifndef SD_H_
#define SD_H_
//--------------------------------------------------
#include "stm32f1xx_hal.h"
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
//--------------------------------------------------
//--------------------------------------------------
#endif /* SD_H_ */
sd.c
#include "sd.h"
//--------------------------------------------------
Подключим нашу библиотеку в файле main.c
/* USER CODE BEGIN Includes */
#include "sd.h"
/* USER CODE END Includes */
Перейдём в файл sd.c и подключим в ней нашу переменную таймера
#include "sd.h"
//-----------------------------------------------
extern volatile uint16_t Timer1;
Создадим функцию включения, в которой мы просто подождём 20 милисекунд для того, чтобы установилось напряжение
//-----------------------------------------------
void SD_PowerOn(void)
{
Timer1 = 0;
while(Timer1<2) //ждём 20 милисекунд, для того, чтобы напряжение стабилизировалось
;
}
//-----------------------------------------------
Также ниже создадим в нём функцию инициализации карты SD
//-----------------------------------------------
uint8_t sd_ini(void)
{
return 0;
}
//-----------------------------------------------
Создадим в заголовочном файле прототипы на эти функции и вызовем их в main.c в главной функции
HAL_TIM_Base_Start_IT(&htim2);
SD_PowerOn();
sd_ini();
Добавим макросы для светодиода, а также для шины SPI в файл sd.h
//--------------------------------------------------
#define CS_SD_GPIO_PORT GPIOA
#define CS_SD_PIN GPIO_PIN_3
#define SS_SD_SELECT() HAL_GPIO_WritePin(CS_SD_GPIO_PORT, CS_SD_PIN, GPIO_PIN_RESET)
#define SS_SD_DESELECT() HAL_GPIO_WritePin(CS_SD_GPIO_PORT, CS_SD_PIN, GPIO_PIN_SET)
#define LD_ON HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); //RED
#define LD_OFF HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); //RED
//--------------------------------------------------
Перейдём в файл sd.c и подключим хендлы шин USART и SPI
#include "sd.h"
//-----------------------------------------------
extern SPI_HandleTypeDef hspi2;
extern UART_HandleTypeDef huart1;
//-----------------------------------------------
Добавим функцию для включения красного светодиода в случае какой-нибудь ошибки в самом верху
//-----------------------------------------------
static void Error (void)
{
LD_ON;
}
//-----------------------------------------------
Ниже этой функции добавим функцию для записи и чтения шины SPI
//-----------------------------------------------
uint8_t SPIx_WriteRead(uint8_t Byte)
{
uint8_t receivedbyte = 0;
if(HAL_SPI_TransmitReceive(&hspi2,(uint8_t*) &Byte,(uint8_t*) &receivedbyte,1,0x1000)!=HAL_OK)
{
Error();
}
return receivedbyte;
}
//-----------------------------------------------
Также добавим ниже этой функции три функции для SPI — чтение, запись и обычный прогон байта
//-----------------------------------------------
void SPI_SendByte(uint8_t bt)
{
SPIx_WriteRead(bt);
}
//-----------------------------------------------
uint8_t SPI_ReceiveByte(void)
{
uint8_t bt = SPIx_WriteRead(0xFF);
return bt;
}
//-----------------------------------------------
void SPI_Release(void)
{
SPIx_WriteRead(0xFF);
}
//-----------------------------------------------
На последнюю функцию SPI_Release необходим будет прототип, поэтому добавим его в файле sd.h. Также в этом файле добавим ещё структуру для хранения свойств нашей карты. Из свойств будет пока только тип карты. Заодно выше этой структуры мы добавим макросы типов карты
//--------------------------------------------------
/* Card type flags (CardType) */
#define CT_MMC 0x01 /* MMC ver 3 */
#define CT_SD1 0x02 /* SD ver 1 */
#define CT_SD2 0x04 /* SD ver 2 */
#define CT_SDC (CT_SD1|CT_SD2) /* SD */
#define CT_BLOCK 0x08 /* Block addressing */
//--------------------------------------------------
typedef struct sd_info {
volatile uint8_t type;//тип карты
} sd_info_ptr;
//--------------------------------------------------
Вернёмся в файл sd.c и добавим там глобальную переменную типа нашей структуры
extern volatile uint16_t Timer1;
sd_info_ptr sdinfo;
Перейдём в функцию инициализации. Задача инициализации — определить тип нашей карты, которых, как мы знаем бывает несколько, а также перевести карту в обычный режим из режима IDLE.
Добавим здесь переменные для счётчика, для виртуального таймера, а также ещё одну переменную для временного хранения значений, выключим светодиод и произведём инициализацию типа карты
uint8_t sd_ini(void)
{
uint8_t i;
int16_t tmr;
uint32_t temp;
LD_OFF;
sdinfo.type = 0;
Сохраним значение делителя шины SPI, установим пока другой делитель и заново инициализируем шину, чтобы уменьшить скорость, так как слишком быструю сразу карта может не понять
sdinfo.type = 0;
temp = hspi2.Init.BaudRatePrescaler;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128; //156.25 kbbs
HAL_SPI_Init(&hspi2);
Затем передадим несколько импульсов на ножку синхронизации SPI. По даташиту нужно не менее 74, мы передадим на всякий случай 80. Это можно сделать обычным прогоном байта по шине SPI. Причём есть ещё требование — ножка выбора должна быть поднята
HAL_SPI_Init(&hspi2);
SS_SD_DESELECT();
for(i=0;i<10;i++) //80 импульсов (не менее 74) Даташит стр 91
SPI_Release();
А вот теперь соберём код, прошьём контроллер и посмотрим результат в программе логического анализатора. Для этого мы в программе Saleae Logic мы настроим на канале 0, отвечающий за ножку CS, триггер, срабатывающий по восходящему фронту, чтобы запись начиналась именно с первым появлением такого импульса на данной ножке.
Нажмём кнопку Start и перезагрузим контроллер. Получим вот такой вот результат
Мы видим 10 пакетов по 8 импульсов. CS у нас в высоком состоянии, импульсы только на шине синхронизации. Импульс на ножке MOSI свидетельствует о включении высокого уровня для передачи 0xFF. Но он нас меньше всего волнует. Растянем немного график колесом мыши
Мы увидим частоту и период импульсов. Частота у нас действительно очень близка к заявленной и составляет 157 килогерц.
Отлично! По идее наша флеш-карта должна уже была понять, что от неё хотят работы именно в режиме SPI.
В следующей части нашего урока мы напишем полностью функцию инициализации карты SD, постоянно проверяя каждый наш шаг на практике.
Предыдущий урок Программирование МК STM32 Следующая часть
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Модуль Micro-SD SPI можно приобрести здесь Micro-SD SPI
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК (нажмите на картинку)
Спасибо Вам за Ваши труды! )))
В rct6 версии есть sdio 4х проводной притом. Наверное имелось ввиду c8t6