STM Урок 219. HAL. STM32F4. FLASH память W25Q. Запись данных. Подключаем LCD
Продолжая тему программирования контроллера STM32F4 и также работу с последовательной FLASH-памятью серии W25Q, мы попытаемся с помощью программы для ПК записать данные в память микросхемы. Причём запишем мы данные из файла, подготовленного для прошивки.
Хочу заверить, что данная работа несложная, так как у нас почти всё для этого в библиотеке уже готово, поэтому было принято решение также для большей информативности задействовать дисплей LCD, который у нас на плате присутствует. Подключим мы его, используя также шину SPI, так как есть планы продолжить данную тему в LL, а там работа с интерфейсом LTDC не предусмотрена.
Схема урока не изменилась, осталась такая же, как и в прошлом занятии
И проект мы за основу также возьмём из прошлого урока с именем SPI_25Q32_CLEAR и присвоим ему новое имя SPI_25Q32_LCD.
Откроем проект в Cube MX и, так как дисплей на нашей плате подключен к шине SPI5, то её мы сейчас и задействуем. Дисплей по шине SPI мы уже подключали в уроке 179. Также мы подключали дисплей по SPI и на плате F429 в уроке 208, правда использовали мы библиотеку LL.
Включим SPI и настроим битрейт

Включим ножку PC2 на выход

То же самое проделаем и с ножками PD12 и PD13

Скорость им настроим максимальную, такую же как и у ножек SPI

Также кое-что поправим из существующего.
Во-первых, давайте поднимем ножку выбора микросхемы, иначе она сразу же начнёт работать

Добавим скорость на SPI1, так как микросхема у нас быстрая, и думаю справится

Также добавим размер стека и кучи

Сгенерируем проект и откроем его в Keil.
Подключим файл w25q_spi.c, настроим программатор на автоперезагрузку и отключим оптимизацию.
Из проекта урока 179 с именем ILI9341_SPI скопируем файлы spi_ili9341.c, spi_ili9341.h, а также файлы шрифтов font8.c, font12.c, font16.c, font20.c, font24.c и fonts.h в соответствующие каталоги нашего нового проекта. Файл spi_ili9341.c добавим в дерево проекта.
В файле main.c подключим нашу библиотеку дисплея
|
1 2 |
#include "w25q_spi.h" #include "spi_ili9341.h" |
В файле spi_ili9341.h внесём следующие изменения.
Здесь добавим расширение и уберём первую букву в имени
#include <cstdlib.h>
Здесь изменим ножки
#define RESET_ACTIVE() HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,GPIO_PIN_RESET)
#define RESET_IDLE() HAL_GPIO_WritePin(GPIOD,GPIO_PIN_12,GPIO_PIN_SET)
#define CS_ACTIVE() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_2,GPIO_PIN_RESET)
#define CS_IDLE() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_2,GPIO_PIN_SET)
#define DC_COMMAND() HAL_GPIO_WritePin(GPIOD,GPIO_PIN_13,GPIO_PIN_RESET)
#define DC_DATA() HAL_GPIO_WritePin(GPIOD,GPIO_PIN_13,GPIO_PIN_SET)
Данный прототип функции удалим
uint16_t TFT9341_RandColor(void);
Перейдём в файл spi_ili9341.c и удалим одноимённую функцию вместе с телом.
Удалим также подключение библиотеки для генератора случайных чисел
extern RNG_HandleTypeDef hrng;
Выше в подключении библиотеки для SPI, а также ниже во всех местах, где попадётся hspi1, исправим на hspi5.
В функции TFT9341_DrawLine уберём здесь плюс, получится
err=dx;
Перейдём в файл main.c и в функции main() произведём инициализацию дисплея
|
1 2 3 |
W25_Ini(); TFT9341_ini(240, 320); TFT9341_FillScreen(TFT9341_BLACK); |
Теперь займёмся записью в память микросхемы.
Для этого идём в файл w25q_spi.c и добавим макрос для команды записи
|
1 2 |
#define W25_READ 0x03 #define W25_PAGE_PROGRAMM 0x02 |
Из тел функций W25_Write_Enable и W25_Write_Disable удалим задержку
cs_reset();
HAL_Delay(1);
Ниже функции W25_Write_Disable добавим функцию включения и отключения защитных битов
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//------------------------------------------------------------- void W25_Set_Block_Protect(uint8_t val) { buf[0] = 0x50; cs_set(); SPI1_Send(buf, 1); cs_reset(); buf[0] = W25_WRITE_STATUS_1; buf[1] = ((val & 0x0F) << 2); cs_set(); SPI1_Send(buf, 2); cs_reset(); } //------------------------------------------------------------- |
Ничего особенного в данной функции нет, она сначала разрешает запись в статусный регистр, а затем включает или отключает нужные биты в нём.
Теперь в функции W25_Erase_Sector мы сначала разблокируем защиту
|
1 2 3 4 |
void W25_Erase_Sector(uint32_t addr) { W25_Wait_Write_End(); W25_Set_Block_Protect(0x00); |
Затем внизу тела функции мы сначала удалим задержку
W25_Wait_Write_End();
HAL_Delay(1);
А вместо этого мы отключим запись и включим защиту
|
1 2 3 |
W25_Wait_Write_End(); W25_Write_Disable(); W25_Set_Block_Protect(0x0F); |
Затем ровно то же самое проделаем с функциями W25_Erase_Block и W25_Erase_Chip.
Ниже функции W25_Erase_Chip добавим функцию записи данных, в которой мы сначала дождёмся готовности микросхемы к операции изменения данных, разблокируем защиту, включим возможность записи и опустим ножку выбора
|
1 2 3 4 5 6 7 8 9 |
//------------------------------------------------------------- void W25_Write_Data(uint32_t addr, uint8_t* data, uint32_t sz) { W25_Wait_Write_End(); W25_Set_Block_Protect(0x00); W25_Write_Enable(); cs_set(); } //------------------------------------------------------------- |
Далее отправляем в нашу микросхему команду на запись и адрес, с которого пишем, и затем сразу же отправляем наши данные
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
cs_set(); buf[0] = W25_PAGE_PROGRAMM; if(w25_info.high_cap) { buf[1] = (addr >> 24) & 0xFF; buf[2] = (addr >> 16) & 0xFF; buf[3] = (addr >> 8) & 0xFF; buf[4] = addr & 0xFF; SPI1_Send(buf, 5); } else { buf[1] = (addr >> 16) & 0xFF; buf[2] = (addr >> 8) & 0xFF; buf[3] = addr & 0xFF; SPI1_Send(buf, 4); } SPI1_Send(data, sz); |
Поднимем ножку, дождёмся окончания процесса записи, запретим запись и включим защиту
|
1 2 3 4 |
cs_reset(); W25_Wait_Write_End(); W25_Write_Disable(); W25_Set_Block_Protect(0x0F); |
По такому же принципу сочиним функцию для записи целой страницы памяти и добавим её ниже
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
//------------------------------------------------------------- void W25_Write_Page(uint8_t* data, uint32_t page_addr, uint32_t offset, uint32_t sz) { if(sz > w25_info.PageSize) sz=w25_info.PageSize; if((offset+sz) > w25_info.PageSize) sz = w25_info.PageSize - offset; page_addr = page_addr * w25_info.PageSize + offset; W25_Wait_Write_End(); W25_Set_Block_Protect(0x00); W25_Write_Enable(); cs_set(); buf[0] = W25_PAGE_PROGRAMM; if(w25_info.high_cap) { buf[1] = (page_addr >> 24) & 0xFF; buf[2] = (page_addr >> 16) & 0xFF; buf[3] = (page_addr >> 8) & 0xFF; buf[4] = page_addr & 0xFF; SPI1_Send(buf, 5); } else { buf[1] = (page_addr >> 16) & 0xFF; buf[2] = (page_addr >> 8) & 0xFF; buf[3] = page_addr & 0xFF; SPI1_Send(buf, 4); } SPI1_Send(data, sz); cs_reset(); W25_Wait_Write_End(); W25_Write_Disable(); W25_Set_Block_Protect(0x0F); } //------------------------------------------------------------- |
В заголовочном файле добавим прототипы на наши обе функции, вернёмся в файл main.c и в функции main() объявим три целочисленные локальные переменные
|
1 2 |
uint8_t dt1[10]; unsigned int cur_page, pageSize, pageCount; |
В бесконечном цикле в той ветке условия, где происходит процесс чтения служебной информации, объявим на дисплее о данном процессе
|
1 2 3 4 5 6 |
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_RESET); TFT9341_SetFont(&Font20); TFT9341_SetBackColor(TFT9341_BLACK); TFT9341_FillScreen(TFT9341_BLACK); TFT9341_String(1,150,"Read Info "); |
Затем в конце ветви отчитаемся об окончании процесса
|
1 2 |
HAL_UART_Transmit(&huart1,rx_buf,strlen((char*)rx_buf),0x1000); TFT9341_String(1,176,"Done!"); |
В следующей ветви, предназначенной для чтения из памяти, добавим аналогичный код
|
1 2 3 4 5 6 |
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_SET); TFT9341_SetFont(&Font20); TFT9341_SetBackColor(TFT9341_BLACK); TFT9341_FillScreen(TFT9341_BLACK); TFT9341_String(1,150,"Read Data "); |
В конце ветви отчитаемся об окончании процесса чтения
|
1 2 3 |
HAL_UART_Transmit(&huart1,rx_buf + w25_info.PageSize / 100,w25_info.PageSize % 100,0x1000); } TFT9341_String(1,176,"Done!"); |
Выше в данной ветви была ошибка в момент отправки последней порции байтов, смещение было только в количестве переданных уже порций, но надо это количество умножить на сто, иначе мы отправляем порцию не из того места буфера
HAL_UART_Transmit(&huart1,rx_buf + w25_info.PageSize / 100 * 100, w25_info.PageSize % 100,0x1000);
В следующей ветви, предназначенной для стирания чипа, добавим также аналогичный код
|
1 2 3 4 5 6 |
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_SET); TFT9341_SetFont(&Font20); TFT9341_SetBackColor(TFT9341_BLACK); TFT9341_FillScreen(TFT9341_BLACK); TFT9341_String(1,150,"Clear Chip "); |
В конце ветви также отчитаемся об окончании процесса
|
1 2 3 |
dt1[0] = 0xEE; HAL_UART_Transmit(&huart1,(uint8_t*)dt1,1,0x1000); TFT9341_String(1,176,"Done!"); |
Далее добавим в наш оператор следующее условие, которое будет срабатывать, когда компьютер пришлёт команду на запись в память, в котором для начала погасим светодиоды
|
1 2 3 4 5 6 7 8 9 10 |
TFT9341_String(1,176,"Done!"); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_SET); } //command Write else if(dt1[0] == 36) { HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_RESET); } |
После того, как погасим светодиоды, примем ещё 8 байтов с информацией от компьютера и также на дисплее напишем о начале процедуры записи
|
1 2 3 4 5 6 7 |
HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_RESET); HAL_UART_Receive(&huart1,(uint8_t*)rx_buf,8,0x1000); TFT9341_SetFont(&Font20); TFT9341_SetBackColor(TFT9341_BLACK); TFT9341_FillScreen(TFT9341_BLACK); TFT9341_String(1,150,"Write Data "); |
Присвоим из буфера количество страниц и размер страницы соответствующим переменным и пошлём компьютеру команду на начало передачи данных для записи в микросхему
|
1 2 3 4 5 |
TFT9341_String(1,150,"Write Data "); pageCount = *(unsigned int*)(rx_buf); pageSize = *(unsigned int*)(rx_buf + 4); dt1[0] = 0xAA; HAL_UART_Transmit(&huart1,(uint8_t*)dt1,1,0x1000); |
Затем добавим бесконечный цикл, в котором примем от компьютера номер записываемой страницы, байты страницы, запишем эти байты в микросхему и отправим компьютеру команду об окончании записи страницы, а если это последняя страница, то выйдем из бесконечного цикла.
|
1 2 3 4 5 6 7 8 9 10 11 |
HAL_UART_Transmit(&huart1,(uint8_t*)dt1,1,0x1000); while(1) { HAL_UART_Receive(&huart1,(uint8_t*)rx_buf,4,0x1000); cur_page = *(unsigned int*)(rx_buf); HAL_UART_Receive(&huart1,(uint8_t*)rx_buf,256,0x1000); W25_Write_Page(rx_buf, cur_page, 0, pageSize); dt1[0] = 0xEE; HAL_UART_Transmit(&huart1,(uint8_t*)dt1,1,0x1000); if(cur_page >= (pageCount - 1)) break; } |
После данного цикла, как обычно, отчитаемся об окончании процесса и зажжём светодиоды
|
1 2 3 4 5 |
if(cur_page >= (pageCount - 1)) break; } TFT9341_String(1,176,"Done!"); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_13, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_14, GPIO_PIN_SET); |
Ну и наконец-то пришло время проверить наши труды.
Программу для ПК, как вы, думаю, поняли, я доработал и её вы также можете скачать по ссылке внизу данной страницы.
Запустим нашу программу, соответственно, перед этим собрав наш код для контроллера и прошив его, и сначала считаем, как всегда, служебную информацию, чтобы нужные нам кнопки стали активными.
Как мы видим на дисплее, информация о начале и об окончании процесса из за его быстрого срабатывания появилась практически мгновенно

Попробуем считать данные из микросхемы, нажав кнопку Read.
Вот начало процесса

А вот окончание

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



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

По окончанию процесса на дисплее мы получим соответствующее сообщение, а также увидим зажжённые светодиоды

Мы можем считать прошивку из платы, записать её в файл и сравнить тот файл, который мы писали в микросхему со считанным. Они должны быть одинаковыми. Я неоднократно в этом уже убедился, так что можете поверить мне на слово.
Итак, на данном занятии мы научились прошивать микросхему серии W25Q, то есть записывать в неё данные. Также нам удалось задействовать дисплей, после чего мы можем выводить на него нужную нам информацию.
Всем спасибо за внимание!
Программа для работы с микросхемой
Отладочную плату можно приобрести здесь STM32F429I-DISCO
Микросхему FLASH-памяти W25Q32FVSSIG SOP8 (10 штук) можно приобрести здесь W25Q32FVSSIG
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)





Добрый день.
А возможно ли прикрепить исходный файл виндовс программы для работы с данным контроллером?