Продолжаем тему программирования контроллера STM32F4 и также продолжаем работу с последовательной FLASH-памятью серии W25Q. И теперь мы попробуем прочитать из данной микросхемы не служебную информацию, так как это мы уже умеем, а полезные данные, которые записаны в её памяти. Для данной цели, конечно же надо, что-то туда для начала записать, а так как это мы ещё программно не умеем, то я записал туда готовую прошивку для какого-то спутникового приёмника с помощью специального программатора. Вы можете либо проделать то же самое, либо взять микросхему уже с записанной информацией. Я решил начать с чтения, а не с записи, так как это несколько легче организовать, ибо чтобы писать информацию, её ещё нужно где-то взять.
Схема наша осталась такая же, как и в прошлом уроке
Изучать процесс чтения данных мы будем в плотном взаимодействии с технической документацией, только изучение документации и написание кода мы объединим в один общий процесс, так как изучать без одновременного кодинга нецелесообразно и материал усваивается хуже, что уже проверено ранее.
Поэтому мы сначала создадим проект урока из проекта прошлого урока с именем SPI_25Q32_INFO и назовём его SPI_25Q32_READ.
Откроем наш проект в Cube MX и, ничего в нём не трогая, сгенерируем проект и откроем его в Cube IDE.
Чтобы не писать весь код в одном модуле, в каталогах Inc и Src создадим соответственно файлы w25q_spi.h и w25q_spi.c следующего содержания
1 2 3 4 5 6 7 8 9 10 |
#ifndef __W25Q_SPI_H #define __W25Q_SPI_H //------------------------------------------------------------- #include "stm32f4xx_hal.h" #include "stdint.h" #include <stdio.h> #include <string.h> //------------------------------------------------------------- //------------------------------------------------------------- #endif /* __W25Q_SPI_H */ |
1 2 |
#include "w25q_spi.h" //------------------------------------------------------------- |
Из файла main.c перенесём в файл w25q_spi.c с последующим удалением из источника все наши макросы, а также подключим переменные структур интерфейсов SPI и UART
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include "w25q_spi.h" //------------------------------------------------------------- #define W25_ENABLE_RESET 0x66 #define W25_RESET 0x99 #define W25_READ 0x03 #define W25_GET_JEDEC_ID 0x9f //------------------------------------------------------------- #define cs_set() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET) #define cs_reset() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET) //------------------------------------------------------------- extern SPI_HandleTypeDef hspi1; extern UART_HandleTypeDef huart1; //------------------------------------------------------------- |
То же самое проделаем вот с этой структурой и переменной
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//------------------------------------------------------------- typedef struct { uint16_t PageSize; uint32_t PageCount; uint32_t SectorSize; uint32_t SectorCount; uint32_t BlockSize; uint32_t BlockCount; uint32_t NumKB; uint8_t SR1; uint8_t SR2; uint8_t SR3; }w25_info_t; w25_info_t w25_info; //------------------------------------------------------------- |
Перенесем также вот этот массив
1 |
char str1[30]; |
Добавим целочисленный массив
1 2 |
char str1[30]; uint8_t buf[10]; |
Также перенесём функции SPI1_Send, SPI1_Recv, W25_Reset, W25_Read_Data , W25_Read_ID и W25_Ini со всеми их телами.
В функциях W25_Reset, W25_Read_Data и W25_Read_ID изменим везде имя массива tx_buf на buf.
В файле w25q_spi.h создадим прототипы для наших функций
1 2 3 4 5 6 7 8 9 |
#include <string.h> //------------------------------------------------------------- void SPI1_Send (uint8_t *dt, uint16_t cnt); void SPI1_Recv (uint8_t *dt, uint16_t cnt); void W25_Reset (void); void W25_Read_Data(uint32_t addr, uint8_t* data, uint32_t sz); uint32_t W25_Read_ID(void); void W25_Ini(void); //------------------------------------------------------------- |
Теперь мы можем собрать и прошить наш проект, чтобы проверить его работоспособность. Если всё нормально работает, то продолжим дальше.
Функция для чтения данных с определённого адреса у нас уже есть, поэтому перейдём в main.c и попробуем ею воспользоваться и считать какие-нибудь данные.
Для начала подключим нашу библиотеку
1 2 |
#include <string.h> #include "w25q_spi.h" |
Затем объявим массив и переменную в функции main()
1 2 3 |
/* USER CODE BEGIN 1 */ char str1[30]; unsigned int addr=0; |
Путём несложного кода считаем несколько байтов с самого начала памяти нашей микросхемы и выведем красиво в терминальную программу через шину USART
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 |
W25_Ini(); for(uint16_t k=0; k<4; k++) { W25_Read_Data(k*256, rx_buf, 256); for(uint8_t i=0; i<16; i++) { addr = k*256 + i*16; sprintf(str1,"%08X: ", addr); HAL_UART_Transmit(&huart1,(uint8_t*)str1,10,0x1000); for(uint8_t j=0; j<16; j++) { sprintf(str1,"%02X", rx_buf[(uint16_t)i*16 + (uint16_t)j]); HAL_UART_Transmit(&huart1,(uint8_t*)str1,2,0x1000); if(j==7) HAL_UART_Transmit(&huart1,(uint8_t*)"|",1,0x1000); else HAL_UART_Transmit(&huart1,(uint8_t*)" ",1,0x1000); } HAL_UART_Transmit(&huart1,(uint8_t*)"| ",1,0x1000); for(uint8_t j=0; j<16; j++) { if ((rx_buf[(uint16_t)i*16 + (uint16_t)j] == 0x0A) || (rx_buf[(uint16_t)i*16 + (uint16_t)j] == 0x0D)) sprintf(str1," "); else sprintf(str1,"%c", (char) rx_buf[(uint16_t)i*16 + (uint16_t)j]); HAL_UART_Transmit(&huart1,(uint8_t*)str1,1,0x1000); } HAL_UART_Transmit(&huart1,(uint8_t*)"\r\n",2,0x1000); } HAL_UART_Transmit(&huart1,(uint8_t*)"\r\n",2,0x1000); } |
Соберём код, прошьём контроллер и увидим вот такую вот картину в терминальной программе
Также давайте напишем для нашего удобства функцию постраничного чтения данных из микросхемы. Для этого вернёмся в файл w25q_spi.c и ниже функции W25_Read_Data добавим следующую функцию, не забывая о прототипе
1 2 3 4 5 |
//------------------------------------------------------------- void W25_Read_Page(uint8_t* data, uint32_t page_addr, uint32_t offset, uint32_t sz) { } //------------------------------------------------------------- |
Первым параметром у данной функции будет указатель на данные, вторым — адрес (в страницах, а не в байтах), третьим — смещение в байтах от реального адреса страницы, четвёртым — количество байтов, которые мы собираемся считать.
Добавим в тело функции небольшую защиту от превышения количества страниц и смещения
1 2 3 4 5 6 |
void W25_Read_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; |
Получим реальный адрес
1 2 |
sz = w25_info.PageSize - offset; page_addr = page_addr * w25_info.PageSize + offset; |
Так как в микросхемах объёмом 512 и 1024 мегабит используется адресация 32-разрядная, то давайте добавим в нашу структуру свойств w25_info_t ещё один параметр
1 2 |
uint8_t SR3; uint8_t high_cap; |
Сначала данный параметр в функции W25_Ini обнулим
1 2 |
id &= 0x0000ffff; w25_info.high_cap = 0; |
А затем установим его в единицу в случае попадания в следующие кейсы
1 2 |
case 0x401A: w25_info.high_cap=1; |
1 2 |
case 0x4019: w25_info.high_cap=1; |
Ничего особенного в чтении целой страницы мы не применяем, так как нет каких-то отдельных команд у микросхемы для данной операции, мы лишь попробуем применить другую команду для чтения байтов с кодом 0Bh
Добавим макрос для данной команды
1 2 |
#define W25_READ 0x03 #define W25_FAST_READ 0x0B |
Вернёмся в функцию W25_Read_Page и запишем данную команду в буфер
1 2 |
page_addr = page_addr * w25_info.PageSize + offset; buf[0] = W25_FAST_READ; |
Запишем в буфер также адрес, обращая теперь внимание на объём микросхемы, и передадим элементы буфера в память микросхемы, дав тем самым команду на начало чтения
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
buf[0] = W25_FAST_READ; 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; buf[5] = 0; cs_set(); SPI1_Send(buf, 6); } else { buf[1] = (page_addr >> 16) & 0xFF; buf[2] = (page_addr >> 8) & 0xFF; buf[3] = page_addr & 0xFF; buf[4] = 0; cs_set(); SPI1_Send(buf, 5); } |
Прочитаем байты и поднимем ножку выбора
1 2 3 4 |
SPI1_Send(buf, 5); } SPI1_Recv(data, sz); cs_reset(); |
Вернёмся в main.c и в функции main() закомментируем чтение с помощью обычной функции
1 |
//W25_Read_Data(k*256, rx_buf, 256); |
Вместо неё попробуем использовать нашу новую
1 2 |
//W25_Read_Data(k*256, rx_buf, 256); W25_Read_Page(rx_buf, k, 0, 256); |
Если мы соберём проект и прошьём контроллер, то мы увидим ту же картину. Теперь мы можем для чтения использовать более защищённую и универсальную функцию, которая будет уже работать и с более объёмной памятью. Также можете поиграться с другими страницами, необязательно самыми первыми.
Итак, на данном уроке нам удалось прочитать из микросхемы W25Q уже не служебные данные, а записанную информацию.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F429I-DISCO
Микросхему FLASH-памяти W25Q32FVSSIG SOP8 (10 штук) можно приобрести здесь W25Q32FVSSIG
Смотреть ВИДЕОУРОК в КгTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Скорейшего Вам выздоровления!!! Возвращайтесь поскорее. Очень полезные и нужные уроки у Вас. Всего самого доброго и здоровья, здоровья, здоровья…