Developer от Бога

DV
Показаны сообщения с ярлыком STM32F4. Показать все сообщения
Показаны сообщения с ярлыком STM32F4. Показать все сообщения

воскресенье, 9 июля 2017 г.

STM32

В последнее время, в мобильных телефонах, планшетах, и в прочих переносимых устройствах используются чипы с ядрами ARM. Если изучить чип одного производителя, то совсем не трудно будет перейти к другому производителю микроконтроллеров. Разница будет только в работе с периферийными блоками в микроконтроллерах.
Микроконтроллер можно представить как маленький компьютер, со своей оперативной и постоянной памятью, а также со своими входами/выходами для общения с внешним миром. Ядра ARM как уже упоминалось ( хоть и разные конечно) схожи от всех производителей, а вот периферию ( постоянную память , АЦП/ЦАП , интерфейсы передачи/приема данных ) производители ставят на свое усмотрение какую хотят.
И Это важно понять сразу, микроконтроллер похож на материнскую плату к которой присоединены разные вышеперечисленные устройства , и эти устройства для программиста - независимы от ядра (их перед работой нужно включать, настраивать как совершенно отдельные элементы, хотя они и находятся в одной микросхеме с ядром).
В микроконтроллерах есть регистры, регистры содержат данные. Одни данные (числа) используются для вычислений, другие данные (числа) для настроек микроконтроллера. В регистры записываются только двоичные числа. Регистры могут быть 8, 16, 32, 64 - битные, это значит что за один такт микроконтроллер может что то сделать с данными в регистре (записать/прочитать, произвести какое то вычисление). Чем больше разрядность регистров, тем большее число может быть обработано/перемещено за один такт.
Данный курс, будет посвящен работе с микроконтроллером STM32F407VG установленный на плате DISCOVERY, на ней есть все необходимое для изучения процессов протекающих внутри микроконтроллера, а также программатор, прошивающий как установленный МК так может использоваться для прошивки других МК. В качестве среды, будем использовать редактор Keil который можно скачать совершенно бесплатно на сайте компании, единственный минус бесплатной версии - это размер кода не должен превышать 32 килобайта. Но написать программу в 32 килобайта будет очень непросто, поэтому в первые несколько лет можно спокойно не заморачиваться по поводу редакторов.

STM32. Программирование на СИ

Прежде чем перейти к стандартной программе с миганиями светодиодов стоит упомянуть о подготовительных действиях. Скачать на официальном сайте редактор Keil , в программе есть методы для скачивания нужных, что то вроде библиотек для работы с нужным микроконтроллером. В даном случае мы, будем использовать микроконтроллер STM32F4 установленный на демонстрационной плате Discovery. Микроконтроллер очень мощный, технологичный, нафаршированный всевозможной периферией и по цене - дешевле многих "раскрученных" 8-ми битных микросхем.
Когда я был на стадии изучения работы с этим микроконтроллером, удивил тот факт, что огромное количество уроков написаны с примерами использующих "филологичесский" способ программирования, а именно с использованием библиотек StdPeriph, это предусматривает изучать огромное количество слов без представления о том как работает микроконтроллер. Ведь намного проще записать:

*((uint32_t*)0x40020C00)=0b1010101000000000;


где сразу видно какие биты и какого регистра включены, а в документации можно увидеть что они делают, чем запоминать огромное количество слов из структур, где иногда ради установки одного бита нужно помнить 5-6 слов. Новичкам, не знакомым со всеми терминами и архитектурой микроконтроллера такой способ довольно запутанный.
Поэтому, никаких библиотек в примерах не будет. При создании проекта в Keil, не используйте имена проектов, документов, и папок в которых есть пробелы, это вызывает ошибку и недоумение у новичков - в чем дело.
Работать будем с широким использованием указателей и адресами.

четверг, 6 июля 2017 г.

STM32. ADC (АЦП). Пример кода

Для самого запуска ADC, в нашем случае это ADC1->8 канал, и его работы по оцифровке входного сигнала нужно совсем немного:
1) Настройка тактирования нужного порта (GPIOB).
2) Настройка входного пина PB0 на котором находится 8 канал ADC1 (см. Документацию вывода ножек. Нас интересует "третье" состояние ножки - аналоговый режим, не путать с режимом альтернативной функции или тем более режимом обычного ввода/вывода). Тут нам понадобится всего один регистр GPIOB_MODER.
3) Настройка тактирования ADC1. Из физической схемы или карты адресного пространства видно, что ADC находятся на шине APB2. В регистрах тактирования RCC_APB2ENR находим бит ADC1EN, устанавливающий тактирование нужного нам ADC (ADC1).
4) Переходим к настройкам самого ADC. Устанавливаем бит ADDON регистра ADC1_CR2, который включит ADC. В этом режиме ADC потребляет незначительное количество энергии, и находится в состоянии покоя.
5) В этот же регистр ADC1_CR2 установим бит CONT (режим непрерывного преобразования).
6) В первые 5 битов регистра ADC1_SQR3 установим номер канала для преобразования (8 канал). В следующие биты этого регистра, задаются другие номера каналов если нужно их считывать по очереди.
7) Устанавливаем бит SWSTART регистра ADC1_CR2, который запускает процесс преобразования.
8) Ждем появления флага EOC в статусном регистре ADC1_SR, сигнализирующий о конце преобразования. Флаг сбрасывается аппаратно после чтения регистра данных ADC1_DR.
9) Читаем преобразованные данные с регистра ADC1_DR.


  1. int main(void){
  2. RCC_GPIO|=0x2;
  3. GPIOB_MODER|=0x3;
  4. RCC_ADC|=0x100;// Установка тактирования ADC1
  5. ADC1_CR2=0x1;// Включаем ADC1
  6. ADC1_CR2|=0x2;// Постоянное преобразование
  7. ADC1_SQR3=8;// Номер канала
  8. ADC1_CR2|=0x40000000;// Запуск процесса преобразования
  9. while(1){
  10. while((ADC1_SR&0x2)!=0x2){}
  11. USART2_DR=ADC1_DR;// Отправляем данные куда нужно
  12. }
  13. }

STM32. ADC (АЦП)

ADC, он же АЦП - превращает аналоговый электрический сигнал в его числовой эквивалент. Максимальная разрядность АЦП в STM32F407VG - 12 бит, чего достаточно даже для неплохой записи звука. Например для диапазона измерений в 3V, шаг его шкалы будет равен:
3/4095=0.00073V
Это значит, АЦП будет улавливать изменение напряжения всего в 0.00073V. Впрочем, такая детализация измерений не всегда нужна, поэтому есть режимы работы АЦП в 10, 8, и 6 бит.
Есть множество настроек работы преобразователя:
Программирование 16 каналов, которые будут сканироваться по очереди, и выдавать значения каждого измерения. Также есть возможность использовать внешний источник запуска преобразователя. Передача преобразованных данных по DMA, настройки таймингов между преобразованиями и многое другое.
Минимальная настройка АЦП совсем не сложная, я бы даже сказал - проще только генератор случайных чисел RNG, со своими тремя регистрами. Хотя благодаря количеству каналов, и соответственно регистров к ним , настройка АЦП кажется сложной.
Есть еще небольшое различие внутри АЦП, это инжектированные и регулярные каналы.
Прерывание как и везде на "периферии" может срабатывать на некоторых событиях, например таких как достижение заданного минимального или максимального значения на входе АЦП (функция watchdog), ну и конечно при готовности данных после преобразования.
STM32F407VG имеет три ADC, с одинаковыми по названиям и функциям регистрами, а также отдельная группа регистров C_ADC которая сообщает о происходящем на всех ADC в целом.
Как и при работе с любой другой периферией, случае использования внешних источников измерения, нужно сделать общий ноль.
Адреса регистров ADC:


  1. //.........ADC1........
  2. #define RCC_ADC *((uint32_t*)0x40023844)
  3. #define ADC1_SR *((uint32_t*)0x40012000)
  4. #define ADC1_CR1 *((uint32_t*)0x40012004)
  5. #define ADC1_CR2 *((uint32_t*)0x40012008)
  6. #define ADC1_SMPR1 *((uint32_t*)0x4001200C)
  7. #define ADC1_SMPR2 *((uint32_t*)0x40012010)
  8. #define ADC1_JOFR1 *((uint32_t*)0x40012014)
  9. #define ADC1_JOFR2 *((uint32_t*)0x40012018)
  10. #define ADC1_JOFR3 *((uint32_t*)0x4001201C)
  11. #define ADC1_JOFR4 *((uint32_t*)0x40012020)
  12. #define ADC1_HTR *((uint32_t*)0x40012024)
  13. #define ADC1_HTR *((uint32_t*)0x40012024)
  14. #define ADC1_LTR *((uint32_t*)0x40012028)
  15. #define ADC1_SQR1 *((uint32_t*)0x4001202C)
  16. #define ADC1_SQR2 *((uint32_t*)0x40012030)
  17. #define ADC1_SQR3 *((uint32_t*)0x40012034)
  18. #define ADC1_JSQR *((uint32_t*)0x40012038)
  19. #define ADC1_JDR1 *((uint32_t*)0x40012048)
  20. #define ADC1_JDR2 *((uint32_t*)0x4001204C)
  21. #define ADC1_JDR3 *((uint32_t*)0x40012050)
  22. #define ADC1_JDR4 *((uint32_t*)0x40012054)
  23. #define ADC1_DR *((uint32_t*)0x4001204C)
  24. //.........ADC2.......
  25. #define ADC2_SR *((uint32_t*)0x40012100)
  26. #define ADC2_CR1 *((uint32_t*)0x40012104)
  27. #define ADC2_CR2 *((uint32_t*)0x40012108)
  28. #define ADC2_SMPR1 *((uint32_t*)0x4001210C)
  29. #define ADC2_SMPR2 *((uint32_t*)0x40012110)
  30. #define ADC2_JOFR1 *((uint32_t*)0x40012114)
  31. #define ADC2_JOFR2 *((uint32_t*)0x40012118)
  32. #define ADC2_JOFR3 *((uint32_t*)0x4001211C)
  33. #define ADC2_JOFR4 *((uint32_t*)0x40012120)
  34. #define ADC2_HTR *((uint32_t*)0x40012124)
  35. #define ADC2_HTR *((uint32_t*)0x40012124)
  36. #define ADC2_LTR *((uint32_t*)0x40012128)
  37. #define ADC2_SQR1 *((uint32_t*)0x4001212C)
  38. #define ADC2_SQR2 *((uint32_t*)0x40012130)
  39. #define ADC2_SQR3 *((uint32_t*)0x40012134)
  40. #define ADC2_JSQR *((uint32_t*)0x40012138)
  41. #define ADC2_JDR1 *((uint32_t*)0x40012148)
  42. #define ADC2_JDR2 *((uint32_t*)0x4001214C)
  43. #define ADC2_JDR3 *((uint32_t*)0x40012150)
  44. #define ADC2_JDR4 *((uint32_t*)0x40012154)
  45. #define ADC2_DR *((uint32_t*)0x4001214C)
  46. //........ADC3......
  47. #define ADC3_SR *((uint32_t*)0x40012200)
  48. #define ADC3_CR1 *((uint32_t*)0x40012204)
  49. #define ADC3_CR2 *((uint32_t*)0x40012208)
  50. #define ADC3_SMPR1 *((uint32_t*)0x4001220C)
  51. #define ADC3_SMPR2 *((uint32_t*)0x40012210)
  52. #define ADC3_JOFR1 *((uint32_t*)0x40012214)
  53. #define ADC3_JOFR2 *((uint32_t*)0x40012218)
  54. #define ADC3_JOFR3 *((uint32_t*)0x4001221C)
  55. #define ADC3_JOFR4 *((uint32_t*)0x40012220)
  56. #define ADC3_HTR *((uint32_t*)0x40012224)
  57. #define ADC3_HTR *((uint32_t*)0x40012224)
  58. #define ADC3_LTR *((uint32_t*)0x40012228)
  59. #define ADC3_SQR1 *((uint32_t*)0x4001222C)
  60. #define ADC3_SQR2 *((uint32_t*)0x40012230)
  61. #define ADC3_SQR3 *((uint32_t*)0x40012234)
  62. #define ADC3_JSQR *((uint32_t*)0x40012238)
  63. #define ADC3_JDR1 *((uint32_t*)0x40012248)
  64. #define ADC3_JDR2 *((uint32_t*)0x4001224C)
  65. #define ADC3_JDR3 *((uint32_t*)0x40012250)
  66. #define ADC3_JDR4 *((uint32_t*)0x40012254)
  67. #define ADC3_DR *((uint32_t*)0x4001224C)
  68. #define ADC_CSR *((uint32_t*)0x40012300)
  69. #define ADC_CCR *((uint32_t*)0x40012304)
  70. #define ADC_CDR *((uint32_t*)0x40012308)

STM32. DMA - SPI - акселерометр

Ниже приведен пример кода работы DMA в связке с SPI, USART и акселерометром MEMS платы DISCOVERY. Процесс работы прост - считывается значения регистров акселерометра, и передается через DMA на периферию USART2, которая отправляет данные через переходник RS-232- >USB на компьютер, и визуализируется в виде графика на дисплее. Чтоб не загружать страницу, сюда небыли помещены #defin-ы с указателями на адреса регистров STM32F407VG, но по названиям думаю тут все понятно, какие регистры используются. О том как определять адреса регистров по документации читайте в начальных статьях по программированию STM32.


  1. void SysTick_Handler(void)
  2. {
  3. GPIOE_BSRR|=0x80000;
  4. DMA2_S3CR|=0x1;
  5. }
  6. void USART_TX(uint32_t y)
  7. {
  8. USART2_DR=y-127;// центрируем график с осей по центру экрана
  9. while (!(USART2_SR&0x80)==0x80){}
  10. }
  11. void SystemInit(void)
  12. {
  13. }
  14. uint8_t x[] ={0xA9,0};//Адрес регистров осей акселерометра которые будут считываться.
  15. uint8_t y[2];
  16. uint8_t z;
  17. uint32_t i;
  18. void USART_ini(void)
  19. {
  20. RCC_GPIO|=0x1;
  21. GPIOA_MODER|=0xA0;
  22. GPIOA_AFRL|=0x7700;
  23. RCC_UART2|=0x20000;
  24. USART2_BRR=0x683;
  25. USART2_CR3|=0x80;
  26. USART2_CR1|=0x8;
  27. NVIC_ISER1|=0x40;
  28. USART2_CR1|=0x2000;
  29. }
  30. void SPI_ini(void)
  31. {
  32. RCC_GPIO|=0x19;
  33. GPIOA_MODER|=0xA800;
  34. GPIOA_AFRL|=0x55500000;
  35. GPIOE_MODER|=0x40;
  36. GPIOE_PUPDR|=0x40;
  37. GPIOE_BSRR|=0x8;
  38. RCC_SPI1|=0x1000;
  39. SPI1_CR1 |= 0x0002 |0x0001;
  40. SPI1_CR1 |=0x0300|0x0028;
  41. SPI1_CR1 |=0x04;
  42. SPI1_CRCPR=7;
  43. SPI1_CR2|=0x3;
  44. SPI1_CR1|=0x0040;
  45. GPIOE_BSRR=0x80000;
  46. SPI1_DR=0x20;
  47. while (!(SPI1_SR&0x1)==0x1){};
  48. z=SPI1_DR;
  49. SPI1_DR=0x97;
  50. while (!(SPI1_SR&0x1)==0x01){}
  51. z=SPI1_DR;
  52. GPIOE_BSRR=0x8;
  53. }
  54. void DMA_ini(void)
  55. {
  56. DMA1_RCC|=0x200000;
  57. DMA2_RCC|=0x400000;
  58. //.....................TX
  59. DMA2_S3CR= 0x06000000;//DMA_Channel_3;
  60. DMA2_S3PAR=0x4001300C;//SPI->DR
  61. DMA2_S3M0AR= (uint32_t)x;
  62. DMA2_S3CR|= 0x40;//DMA_DIR_MemoryToPeripheral;
  63. DMA2_S3NDTR=2;
  64. DMA2_S3CR|=0x0400;
  65. DMA2_S3CR|=0x10000;
  66. //DMA2_S3CR|=0x8;
  67. //DMA2_S3CR|=0x100;//DMA_CIRCULAR
  68. DMA2_S3CR|=0x10;
  69. NVIC_ISER1=0x8000000;
  70. DMA2_S3CR|=0x10;
  71. //....................RX
  72. DMA2_S0CR= 0x06000000;//DMA_Channel_3;
  73. DMA2_S0PAR=0x4001300C;//SPI->DR
  74. DMA2_S0M0AR=(uint32_t)y;
  75. DMA2_S0CR|= 0;//peripheralToMemory
  76. DMA2_S0NDTR=2;
  77. DMA2_S0CR|=0x400;
  78. DMA2_S0CR|=0x10000;
  79. //DMA2_S0CR|=0x100;
  80. DMA2_S0CR|=0x10;
  81. NVIC_ISER1=0x1000000;
  82. DMA2_S0CR|=0x10;
  83. } void DMA2_Stream0_IRQHandler(void)//...........SPI->RX
  84. {
  85. GPIOE_BSRR=0x8;
  86. DMA2_LIFCR=0x20;
  87. USART_TX(y[1]);
  88. }
  89. void DMA2_Stream3_IRQHandler(void)//...........SPI->TX
  90. {
  91. GPIOE_BSRR=0x80000;
  92. if((DMA2_LISR&0x8000000)==0x8000000)
  93. {DMA2_LIFCR=0x8000000;
  94. DMA2_S0CR|=0x1;}
  95. }
  96. void DMA1_Stream6_IRQHandler(void)
  97. {
  98. if((DMA_HISR&0x200000)==0x200000)
  99. DMA_HIFCR=0x200000;
  100. }
  101. int main(void)
  102. {
  103. USART_ini();
  104. SPI_ini();
  105. DMA_ini();
  106. GPIOD_MODER|=0x55000000;
  107. GPIOD_OTYPER|=0x55000000;
  108. SysTic_RVR=0xFFF;
  109. SysTic_CVR=0x0;
  110. SysTic_CTRL_Status=0x10007;
  111. while(1)
  112. {
  113. GPIOD_ODR=0xF000;
  114. for(i=0;i<1000000;i++){};
  115. GPIOD_ODR=0x0;
  116. for(i=0;i<1000000;i++){};
  117. }
  118. }

STM32. DMA USART

В даном примере мы рассмотрим передачу USART через DMA , на компьютер. Как обсуждалось в статье по передачи данных USART , использовался переходник RS-232->USB , а также терминал в компьютере (скачать который без труда можно в интернете) симулирующий работу COM порта. Сложного в работе связки USART->DMA ничего нет, все тоже самое как в обычной передаче USART , но нужно будет еще провести иннициализацию DMA. Традиционно в примере не будут использоваться библиотеки типа StdPeriph , от которых код только увеличивается, гораздо лаконичнее и очевиднее число установленное в соответствующий регистр.
Программа следующая: светодиоды мигают, и паралельно идет передача DMA. Процессор вмешивается только когда вызывается прерывание.

  1. void SystemInit(void)
  2. {
  3. }
  4. char buffer[]={"iVARIOUS.COM"};// Строка, которую будем передавать
  5. void GPIOD_ini(void) //Процессор занят миганием светодиодов, в то время как DMA передает данные
  6. {
  7. RCC_GPIO|=0x8;
  8. GPIOD_MODER=0x55000000;
  9. GPIOD_PUPDR=0x55000000;
  10. }
  11. void USART2_ini(void)// иннициализация USART2
  12. {
  13. RCC_GPIO|=0x1;// Тактирование порта A, на котором ножки USART2
  14. GPIOA_MODER|=0xA0;
  15. GPIOA_AFRL|=0x7700;
  16. RCC_UART2|=0x20000;
  17. USART2_BRR=0x683; //BaudRate=9600
  18. USART2_CR3|=0x80;
  19. USART2_CR1|=0xC;// включаем передачу и прием
  20. NVIC_ISER1|=0x40;//Прерывание
  21. USART2_CR1|=0x2000;//Запуск USART2
  22. }
  23. void DMA_ini(void)
  24. {
  25. DMA1_RCC|=0x200000;
  26. DMA_S6CR= 0x08000000; //DMA_Channel_4;
  27. DMA_S6PAR=0x40004404;
  28. DMA_S6M0AR= (uint32_t)buffer;
  29. DMA_S6CR|= 0x40; //DMA_DIR_MemoryToPeripheral;
  30. DMA_S6NDTR=sizeof(buffer);
  31. DMA_S6CR|=0x400;
  32. DMA_S6CR|=0x10000;
  33. DMA_S6CR|=0x100;
  34. NVIC_ISER0=0x20000;
  35. DMA_S6CR|=0x10;
  36. }
  37. //------------------
  38. void DMA1_Stream6_IRQHandler(void)
  39. {
  40. if ((DMA_HISR&0x200000) == 0x200000)
  41. {
  42. DMA_HIFCR=0x200000;//DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);
  43. }
  44. }
  45. //------------------
  46. int main(void)
  47. { uint32_t i=0;
  48. USART2_ini();
  49. DMA_ini();
  50. GPIOD_ini();
  51. DMA_S6CR|=0x1;
  52. while(1)
  53. {
  54. GPIOD_ODR=0xA000;
  55. for(i=0;i<1000000;i++){};
  56. GPIOD_ODR=0;
  57. for(i=0;i<1000000;i++){};
  58. }
  59. }
  60.  

STM32. Регистры DMA

DMA_LISR - Статусный регистр прерываний, сообщающий о событиях при передаче данных.
TCIFx: прерывание по завершению передачи потока х (х = 3..0)
Этот бит устанавливается аппаратно. Он сбрасывается программно записью 1 в соответствующий бит DMA_LIFCR регистра.
0: Передача не завершена, событие на потоке х.
1: передача завершена, событие на поток х.
HTIFx: флаг прерывания половины передачи на потоке х (х = 3..0)
Этот бит устанавливается аппаратно. Он сбрасывается программно записью 1 в соответствующий бит DMA_LIFCR регистра.
0: Половина данных пока не передалась на потоке х.
1: Передалась половина данных на потоке х.

TEIFx: флаг прерывания по ошибке передачи на потоке х (х = 3..0)
Этот бит устанавливается аппаратно. Он сбрасывается программно записью 1 в соответствующий бит DMA_LIFCR регистра.
0: нет ошибок передачи на потоке х.
1: Произошла ошибка передачи на потоке х.

DMEIFx: флаг прерывания по ошибке в режиме direct на потоке х (х = 3..0) Этот бит устанавливается аппаратно. Он сбрасывается программно записью 1 в соответствующий бит DMA_LIFCR регистра.
0: Нет ошибки работы в режиме direct на потоке х.
1: Произошла ошибка в режиме Direct на потоке х.

FEIFx: Флаг прерывания по ошибке FIFO на потоке х (х = 3..0)
Этот бит устанавливается аппаратно. Он сбрасывается записью 1 с соответствующий бито DMA_LIFCR регистр.
0: Ошибок в FIFO на потоке х нет.
1: Произошла ошибка FIFO на потоке х.

DMA_HISR - подобен регистру DMA_LISR , но для потоков 4-7.


DMA_LIFCR - Регистр сброса флагов прерывания, на потоках 0-3;
CTCIF: Очистка флага прерывания после полной передачи.
CHTIF: Очистка флага прерывания после половины передачи.
CTEIF: Очистка флага прерывания по возникновению ошибки.
CDMEIF: Очистка флага прерывания по возникновению ошибки в режиме Direct.
CFEIF: Очистка флага прерывания по возникновению ошибки в режиме FIFO.
Запись в этот бит очищает соответствующий флаг в регистре DMA_LISR.

DMA_HIFCR - Регистр сброса флагов прерывания аналогичен DMA_LIFCR, но для потоков 4-7;

DMA_CR - Регистр конфигаруции с битами: CHSEL выбор каналов, DIR направление передачи,TEIE/HTIE установки прерываний после передачи, установка шаг (приращение) адреса памяти после чтения/записи регистров. EN - запуск/отключения работы DMA.

DMA_NDTR - Регистр, в котором записывается общий обьем передаваемых данных.

DMA_PAR - Адрес периферии из которого будут переписыватся данные, или в который данные будут записыватся.

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

DMA_FIFO - Регистр управления DMA в режиме FIFO.


//......DMA....
//.............
#define DMA2_RCC *((uint32_t*)0x40023830)//|=0x400000;
#define DMA2_LISR *((uint32_t*)0x40026400)
#define DMA2_HISR *((uint32_t*)0x40026404)
#define DMA2_LIFCR *((uint32_t*)0x40026408)
#define DMA2_HIFCR *((uint32_t*)0x4002640C)
#define DMA2_S0CR *((uint32_t*)0x40026410)
#define DMA2_S0NDTR *((uint32_t*)0x40026414)
#define DMA2_S0PAR *((uint32_t*)0x40026418)
#define DMA2_S0M0AR *((uint32_t*)0x4002641C)
#define DMA2_S0M1AR *((uint32_t*)0x40026420)
#define DMA2_S0FCR *((uint32_t*)0x40026424)
#define DMA2_S1CR *((uint32_t*)0x40026428)
#define DMA2_S1NDTR *((uint32_t*)0x4002642C)
#define DMA2_S1PAR *((uint32_t*)0x40026430)
#define DMA2_S1M0AR *((uint32_t*)0x40026434)
#define DMA2_S1M1AR *((uint32_t*)0x40026438)
#define DMA2_S1FCR *((uint32_t*)0x4002643C)
#define DMA2_S2CR *((uint32_t*)0x40026440)
#define DMA2_S2NDTR *((uint32_t*)0x40026444)
#define DMA2_S2PAR *((uint32_t*)0x40026448)
#define DMA2_S2M0AR *((uint32_t*)0x4002644C)
#define DMA2_S2M1AR *((uint32_t*)0x40026450)
#define DMA2_S2FCR *((uint32_t*)0x40026454)
#define DMA2_S3CR *((uint32_t*)0x40026458)
#define DMA2_S3NDTR *((uint32_t*)0x4002645C)
#define DMA2_S3PAR *((uint32_t*)0x40026460)
#define DMA2_S3M0AR *((uint32_t*)0x40026464)
#define DMA2_S3M1AR *((uint32_t*)0x40026468)
#define DMA2_S3FCR *((uint32_t*)0x4002646C)
#define DMA2_S4CR *((uint32_t*)0x40026470)
#define DMA2_S4NDTR *((uint32_t*)0x40026474)
#define DMA2_S4PAR *((uint32_t*)0x40026478)
#define DMA2_S4M0AR *((uint32_t*)0x4002647C)
#define DMA2_S4M1AR *((uint32_t*)0x40026480)
#define DMA2_S4FCR *((uint32_t*)0x40026484)
#define DMA2_S5CR *((uint32_t*)0x40026488)
#define DMA2_S5NDTR *((uint32_t*)0x4002648C)
#define DMA2_S5PAR *((uint32_t*)0x40026490)
#define DMA2_S5M0AR *((uint32_t*)0x40026494)
#define DMA2_S5M1AR *((uint32_t*)0x40026498)
#define DMA2_S5FCR *((uint32_t*)0x4002649C)
#define DMA2_S6CR *((uint32_t*)0x400264A0)
#define DMA2_S6NDTR *((uint32_t*)0x400264A4)
#define DMA2_S6PAR *((uint32_t*)0x400264A8)
#define DMA2_S6M0AR *((uint32_t*)0x400264AC)
#define DMA2_S6M1AR *((uint32_t*)0x400264B0)
#define DMA2_S6FCR *((uint32_t*)0x400264B4)
#define DMA2_S7CR *((uint32_t*)0x400264B8)
#define DMA2_S7NDTR *((uint32_t*)0x400264BC)
#define DMA2_S7PAR *((uint32_t*)0x400264C0)
#define DMA2_S7M0AR *((uint32_t*)0x400264C4)
#define DMA2_S7M1AR *((uint32_t*)0x400264C8)
#define DMA2_S7FCR *((uint32_t*)0x400264CC)

STM32. DMA controller

DMA Контроллер - блок , обеспечивающий коммутацию между периферийными блоками и оперативной, flash и другой памятью без использования ресурсов центрального процессора. Главная его задача обеспечить высокую скорость передачи данных из периферии в память, из памяти в периферию, и из памяти в память не отвлекая на эту рутинную работу процессор, который в это время может выполнять более вычислительные задачи.
DMA контроллер содержит 8 потоков для данных , в каждом потоке по 8 каналов, на которых находятся источники данных: периферия и память. DMA контроллер не может через один поток пропускать два и больше канала (два и больше разных источника данных - от периферийных блоков или памяти) в один момент времени, так как это нарушает саму суть DMA - обеспечить максимальную скорость передачи данных. В принципе это и не нужно, так как неважно будут ли два источника передавать данные одновременно или по очереди - суммарная скорость теоретически будет одинакова.
Как уже говорилось в прошлых статьях, любой периферийный блок представляет собой совершенно независимое устройство, хотя и интегрированное в общий корпус с процессором. Поэтому передачу и контроль за передачей данных спокойно можно возложить на DMA, а не отвлекать центральный процессор чтоб подсчитывать байты, следить за адресами и передачей данных в целом. Роль процессора заключается только в том, чтоб указать DMA, сколько и куда нужно переместить данных. После проделанной работы DMA сообщит процессору что все готово и можно действовать с учетом дальнейшей программы.

//......DMA....
//.............
#define DMA1_RCC *((uint32_t*)0x40023830)//|=0x200000;
#define DMA_LISR *((uint32_t*)0x40026000)
#define DMA_HISR *((uint32_t*)0x40026004)
#define DMA_LIFCR *((uint32_t*)0x40026008)
#define DMA_HIFCR *((uint32_t*)0x4002600C)
#define DMA_S0CR *((uint32_t*)0x40026010)
#define DMA_S0NDTR *((uint32_t*)0x40026014)
#define DMA_S0PAR *((uint32_t*)0x40026018)
#define DMA_S0M0AR *((uint32_t*)0x4002601C)
#define DMA_S0M1AR *((uint32_t*)0x40026020)
#define DMA_S0FCR *((uint32_t*)0x40026024)
#define DMA_S1CR *((uint32_t*)0x40026028)
#define DMA_S1NDTR *((uint32_t*)0x4002602C)
#define DMA_S1PAR *((uint32_t*)0x40026030)
#define DMA_S1M0AR *((uint32_t*)0x40026034)
#define DMA_S1M1AR *((uint32_t*)0x40026038)
#define DMA_S1FCR *((uint32_t*)0x4002603C)
#define DMA_S2CR *((uint32_t*)0x40026040)
#define DMA_S2NDTR *((uint32_t*)0x40026044)
#define DMA_S2PAR *((uint32_t*)0x40026048)
#define DMA_S2M0AR *((uint32_t*)0x4002604C)
#define DMA_S2M1AR *((uint32_t*)0x40026050)
#define DMA_S2FCR *((uint32_t*)0x40026054)
#define DMA_S3CR *((uint32_t*)0x40026058)
#define DMA_S3NDTR *((uint32_t*)0x4002605C)
#define DMA_S3PAR *((uint32_t*)0x40026060)
#define DMA_S3M0AR *((uint32_t*)0x40026064)
#define DMA_S3M1AR *((uint32_t*)0x40026068)
#define DMA_S3FCR *((uint32_t*)0x4002606C)
#define DMA_S4CR *((uint32_t*)0x40026070)
#define DMA_S4NDTR *((uint32_t*)0x40026074)
#define DMA_S4PAR *((uint32_t*)0x40026078)
#define DMA_S4M0AR *((uint32_t*)0x4002607C)
#define DMA_S4M1AR *((uint32_t*)0x40026080)
#define DMA_S4FCR *((uint32_t*)0x40026084)
#define DMA_S5CR *((uint32_t*)0x40026088)
#define DMA_S5NDTR *((uint32_t*)0x4002608C)
#define DMA_S5PAR *((uint32_t*)0x40026090)
#define DMA_S5M0AR *((uint32_t*)0x40026094)
#define DMA_S5M1AR *((uint32_t*)0x40026098)
#define DMA_S5FCR *((uint32_t*)0x4002609C)
#define DMA_S6CR *((uint32_t*)0x400260A0)
#define DMA_S6NDTR *((uint32_t*)0x400260A4)
#define DMA_S6PAR *((uint32_t*)0x400260A8)
#define DMA_S6M0AR *((uint32_t*)0x400260AC)
#define DMA_S6M1AR *((uint32_t*)0x400260B0)
#define DMA_S6FCR *((uint32_t*)0x400260B4)
#define DMA_S7CR *((uint32_t*)0x400260B8)
#define DMA_S7NDTR *((uint32_t*)0x400260BC)
#define DMA_S7PAR *((uint32_t*)0x400260C0)
#define DMA_S7M0AR *((uint32_t*)0x400260C4)
#define DMA_S7M1AR *((uint32_t*)0x400260C8)
#define DMA_S7FCR *((uint32_t*)0x400260CC)

STM32. Код работы таймера Basic TIM 6

В прошлой статье были разобраны регистры таймеров TIM6 и TIM7, а также их адреса. Следующий код содержит практический пример кода работы таймера TIM 6.
Таймеры внутри подключены к ЦАП и могут отслеживать сигналы через свои триггеры. Основная регулировка времени срабатывания таймера зависит от настроек деления частоты, а также значения счетчика таймера. Вместе они содержат основную систему отсчета, и способны компенсировать друг друга при разных обстоятельствах.

  1.  void TIM6_DAC_IRQHandler(void)
  2. {
  3. if ((TIM6_SR&0x1) != 0)// Проверяем чем именно было вызвано прерывание
  4. {
  5. TIM6_SR=0;// Сбрасывам прерывание, программа отработает до конца и выйдет с состояния прерывания.
  6. }
  7. *((uint32_t*)0x40020C14)=0xF000;//Зажигаем светодиоды
  8. for (i=0;i<1000000;i++);
  9. *((uint32_t*)0x40020C14)=0;
  10. }
  11. int main (void)
  12. {
  13. GPIOD_MODER=0x55000000;
  14. GPIOD_PUPDR=0x55000000;
  15. RCC_GPIO=0x8;
  16. RCC_TIM6=0x10;// тактирование таймера
  17. TIM6_PSC = 20000-1;//Деление тактовой частоты
  18. TIM6_CR1= 0;
  19. TIM6_ARR = 1000;// значение к которому будет заполнятся счетчик
  20. NVIC_ISER1=0x400000;/*номер прерывания = 54, значение берем из документации в таблице (регистры NVIC_ISER 32- битные, очевидно что номер прерывания 54 попадает в диапазон второго регистра NVIC_ISER1 ) */
  21. TIM6_DIER=0x1;//Bit 0 UIE=1: Разрешение прерывания
  22. TIM6_CR1|=0x1;//Bit 0 CEN: Запуск счетчика
  23. While(1){
  24. //Код работы программы
  25. }
  26. }

STM32. Таймеры

Таймеры TIM6 и TIM7 состоят из 16-битного счетчика автоперезагрузки и программируемым делителем частоты. Предназначены для

использования в качестве общих таймеров базовой генерации времени, а также для специализированного использования в области

преобразовании из цифровых сигналов в аналоговые для ЦАП. Таймеры являются полностью независимыми (периферией) и не

используют никак ресурсы других систем. <br>
Согласно документации, таймеры TIM6 и TIM7 включают в себя:<br><br>

-16-битный автоперезагрузчик upcounter.<br>
-16-битный программируемый делитель, значение которого можно устанавливать в диапазоне от 1 до 65536, изменять в любой

момент времени.<br>
-Схему синхронизации для запуска Цифро-аналоговых преобразователей.<br>
-Прерывание от генерации DMA на события обновления, переполнения счетчика.<br><br><br>
Таймеры содержат следующие регистры:<br><br>
<b>TIMx_PSC</b> содержит значение  которым делит тактовую частоту для счетчика.<br>
<b>TIM_SR</b> содержит флаг (бит) значение которого указывает было ли переполнение или опустошение счетчика, при условии

что установлен бит UIDS регистра TIMx_CR1.<br>
<b>TIMx_CR1</b> содержит общие настройки таймеров:
CEN бит включает таймер, UDIS бит  вкл/откл обновления значений таймеров, URS бит - устанавливает  источник обновления UEV (может быть вызван опустошением или переполнением счетчика, установкой бита UG, или генерацией в случае использования таймера как подчиненного). Бит OPM устанавливает одноразовый отчет. ARPE бит устанавливает буферизацию значения обновления, если в работе предусмотрено изменения значений счетчика в ходе работы программы.

STM32. Работа с акселерометром

Акселерометр LIS3DSH установленный на плате Discovery STM32F407VG прекрасно подходит для изучения интерфейсов SPI. Для лучшего изучения устройств и разных датчиков, необходимо будет купить переходник RS232->USB, чтоб в режиме реального времени смотреть график значений с осей акселерометра, и установить терминал имитирующий COM порт на компьютере.





Программа SPI передача проходит следующим образом:
Инициализация.
1)Ножка PE3 прижимается к нулю, что символизирует начало сеанса.
2)В SPI1_DR отправляем значение 0x20, это адрес регистра в акселерометре CTRL_REG4.
3)Ждем в цикле while пока не установится бит RXNE регистра SPI_SR в микроконтроллере, что будет символизировать о получении в ответ данных от акселерометра.
4)Читаем данные которые пришли в этот же регистр SPI1_DR, они нам не нужны, но прочитать нужно (SPIData=SPI1_DR;).
5)Тут же отправляем в регистр SPI1_DR настройки - значение для ранее подготовленного регистра CTRL_REG4 (установки осей, включения скорости считывания, что является одновременно и стартом работы акселерометра, значение = 0х97).
6)Ждем пока не придут ответные данные (они нам не нужны, но прочитать их нужно).
7)PE3 Ножку прижимаем к единице, что символизирует конец сеанса.

Чтение осей.
1)Ножка PE3 прижимается к нулю, что символизирует начало сеанса.
2)В SPI1_DR отправляем значение 0xA9 (адрес регистра OUT_X_H в акселерометре с выставленным старшим битом "чтение").
3)Ждем в цикле while пока не установится бит RXNE регистра SPI_SR в микроконтроллере, что будет символизировать о получении в ответ данных от акселерометра.
4)Читаем данные которые пришли в этот же регистр SPI1_DR, они нам не нужны, это всего-лишь данные в ответ на отправку адреса (mems_data = SPI1_DR;).
5)Тут же отправляем в регистр SPI1_DR пустое значение - чтоб запустить процесс отправки уже полезных данных с осей акселерометра нам, SPI1_DR=0x00;.6)Ждем пока не придут ответные данные, это уже данные значения осей акселерометра.
7)PE3 Ножку прижимаем к единице, что символизирует конец сеанса.



  1. void SystemInit(void)
  2. {
  3. }
  4. void USART2_ini(void)
  5. {
  6. RCC_GPIO|=0x1;//A
  7. GPIOA_MODER|=0xA0;
  8. GPIOA_AFRL|=0x7700;
  9. RCC_UART2|=0x20000;
  10. USART2_BRR=0x683;
  11. USART2_CR3|=0x80;
  12. USART2_CR1|=0xC;
  13. USART_Mode_Rx;
  14. USART2_CR1|=0x2000;
  15. }
  16. void SPI_ini(void)
  17. {
  18. RCC_GPIO=0x19;
  19. GPIOA_MODER|=0xA800;
  20. GPIOA_OSPEEDR=0xA800;
  21. GPIOA_AFRL=0x55500000;
  22. GPIOE_MODER|=0x40;
  23. GPIOE_OSPEEDR=0x80;
  24. GPIOE_PUPDR=0x40;
  25. GPIOE_BSRR=0x8;
  26. RCC_SPI1=0x1000;
  27. SPI1_CR1 |= 0x0002 |0x0001;
  28. SPI1_CR1 |=0x0300|0x0028;
  29. SPI1_CR1 |=0x04 ;
  30. SPI1_CRCPR=7;
  31. //SPI1_CR2|=0x3;
  32. SPI1_CR1|=0x0040;
  33. }
  34. void MEMS_ini(void)
  35. {
  36. uint8_t SPIData;
  37. GPIOE_BSRR=0x80000;
  38. SPI1_DR=0x20;
  39. while (!(SPI1_SR&0x1)==0x1);
  40. SPIData=SPI1_DR;
  41. SPI1_DR=0x97;
  42. while (!(SPI1_SR&0x1)==0x01);
  43. SPIData =SPI1_DR;
  44. GPIOE_BSRR=0x8;
  45. }
  46. short int mems_data(read_reg)
  47. {
  48. uint8_t mems_data;
  49. GPIOE_BSRR=0x80000;
  50. SPI1_DR=read_reg;
  51. while (!(SPI1_SR&0x1)==0x1);
  52. mems_data = SPI1_DR;
  53. SPI1_DR=0x00;
  54. while (!(SPI1_SR&0x1)==0x01);
  55. mems_data=SPI1_DR;
  56. GPIOE_BSRR=0x8;
  57. return -(mems_data+128);
  58. }
  59. void USART2_tx(data)
  60. {
  61. USART2_DR=data;
  62. while(!(USART2_SR&0x40)){};
  63. }
  64. int main(void)
  65. { uint8_t x=0xA9;// Адрес регистра нужной оси
  66. USART2_ini();
  67. SPI_ini();
  68. MEMS_ini();
  69. while(1)
  70. {
  71. USART2_tx(mems_data(x));
  72. }}
  73.  

STM32. SPI интерфейс

SPI Самый популярный интерфейс передачи данных, может работать в режиме протокола SPI или аудио протокола I2S. Большинство датчиков, различных сенсоров и дисплеев общаются с микроконтроллером через этот интерфейс. Он несколько сложнее интерфейса USART , но обеспечивает более надежную передачу данных.
Первое чем отличается от интерфейса USART, это назначение "Ведущей" (или Master) микросхемы, которая задает тактирование и управляет приемом/передачей информации между двумя и больше микросхемами. Сколько бы ни было микросхем на связи SPI, обязательно должен быть только один Master (в один момент времени).
SPI работает в режиме приема/передачи формата 8 и 16 бит полудуплекс или полный дуплекс, использует 4 вывода:

MOSI - Master Output Slave Input, если микросхема является Ведущей, этот вывод должен быть подключен к входу Подчиненной микросхемы.
MISO - Master Input Slave Output, если микросхема является Ведущей, этот вывод принимает сигнал от Подчиненной микросхемы ( Ведомого )
SCK - вывод тактирования.
NSS - вывод управления (вкл/выкл) Подчиненных (Slave) микросхем, если ножка не настроена на автоматическое переключение, в начале связи ее уровень нужно устанавливать в ноль или наоборот в единицу, в зависимости от настроек бита.
Бит CPOL и CPHA отвечает за отношение фазы сигнала к тактированию, от него зависит при каком логическом переходе (с 0 в 1 , или наоборот) начнется отчет начала связи. Вариантов есть четыре, кроме отслеживания по переходу есть возможность настройки по переднему и задних фронтах. Эти установки должны быть одинаковы на всех устройствах участвующих в передаче. На качество связи данные биты могут повлиять если графики сигнала тактовых импульсов на фронтах не одинаковы на разных устройствах, и можно выбрать ту модель синхронизации, максимально подходящей для данных устройств.

Сложность работы SPI является еще и в том, что он не может передавать данные " в никуда ", или принимать данные из " неоткуда " как USART. Передача данных SPI начинается с передачи адреса регистра , куда эти данные следует записать или прочитать, и только потом уже отправлять значения в регистры. Поэтому нужно знать адресное пространство других устройств, чтоб с ними работать. Если микросхем много, то прежде всего нужно еще и указывать адрес устройства с которым будет сеанс связи.
Вся работа према-передачи происходит через регистр смещения, с одной стороны из него данные выходят, с другой стороны одновременно, побитово, в него данные загружаются. Главное не перепутать какие данные нужны, а какие просто мусор - загрузились одновременно при отправке данных.


//............SPI.......
#define RCC_SPI1 *((uint32_t*)0x40023844)//RCC
#define SPI1_CR1 *((uint32_t*)0x40013000)
#define SPI1_CR2 *((uint32_t*)0x40013004)
#define SPI1_SR *((uint32_t*)0x40013008)
#define SPI1_DR *((uint32_t*)0x4001300C)
#define SPI1_CRCPR *((uint32_t*)0x40013010)
#define SPI1_RXCRCR *((uint32_t*)0x40013014)
#define SPI1_TXCRCR *((uint32_t*)0x40013018)
#define SPI1_CTRL_REG1 *((uint32_t*)0x40013020)

STM32. Передача данных по USART

В демонстрационной плате Discovery на чипе STM32F407VG нет периферии для тестирования UART, поэтому чтоб в полной мере изучить передачу данных этим интерфейсом понадобятся некоторые дополнительные приспособления. Конечно можно обойтись и внутренними резервами, передавая сигнал от ножки к ножке замкнув их, но для более наглядной демонстрации лучше купить переходник RS-232->USB, и используя разные терминалы симулирующие работу COM порта на компьютере передавать данные на микроконтроллер, а также принимать от микроконтроллера видя сразу результат принятого текста.
В даном примере мы будем работать с периферией USART2 в STM32F407VG.
Начинается код традиционно с установкой тактирования выводов и самого модуля UART2 в регистре RCC_UART2. Так как порт GPIOA уже имеет некоторое значение по умолчанию в регистре GPIOA_MODER, последующие значения записываем через оператор "ИЛИ". Выводы UART2 отвечающие прием/передача (RX/TX) соответственно занимают пины PA2,PA3 и попадают в диапазон регистра GPIOA_AFRL (ножки 0-7). Номер альтернативной функции для UART2 равен 7 (вместе с UART1 и UART3, см . figure 26, Selecting an alternate function в документации). В GPIOA_AFRL устанавливаем значение соответствующее для AF7- 0x7700; (0b0111 - в биты соответствующие пинам PA2,PA3):



В регистр скорости передачи данных устанавливаем USART2_BRR=0x683; как получить данное значение читайте в STM32. USART


  1. int main (void)
  2. {
  3. uint32_t i,k=0;
  4. RCC_UART2=0x20000; //тактирование UART2
  5. //.инициализация портов для UART2…
  6. GPIOA_MODER|=0xA0; //выводы UART как альтернативная функция
  7. GPIOA_AFRL=0x7700; //номер альтернативной функции
  8. //…………….инициализация UART2………
  9. USART2_BRR=0x683; //скорость передачи/приема
  10. USART2_CR1=0xC; //включить передачу и прием
  11. USART2_CR1|=0x2000; //запуск UART, идет поиск стартового бита
  12. while(1)
  13. {
  14. while(!(USART2_SR&0x20)); //проверка бита RXNE на наличие принятых данных
  15. k=USART2_DR; //чтение принятых данных с компьютера
  16. if (k==0x39) //анализ полученных данных
  17. GPIOD_ODR=0xF000;
  18. else
  19. {
  20. GPIOD_ODR=0;
  21. }
  22. USART2_DR=k; //отправка данных
  23. while(!(USART2_SR&0x40)){}; // проверка бита TC отправлены ли данные
  24. }
  25. }

STM32. Режим альтернативной функции

Режим альтернативной функции, это что то вроде режима "умной" ножки. После установки в регистре GPIOx_MODER настройки альтернативной функции - ножка уже знает что и как делать, для работы нужного периферийного блока: подавать тактирование, принимать сигнал и т.д все это ножки будут исполнять сразу после запуска нужного периферийного блока и настраивать их на эти действия не нужно. Перед установкой альтернативного режима, в таблице с картой выводов, необходимо узнать какие ножки использует нужная периферия ( например USART2 который будем использовать для передачи/приема в следующей статье, использует PA0,PA1,PA2,PA3 -седьмой номер альтернативной функции AF7, см. таблицу). По умолчанию, ножки портов настроены на вход , устанавливая альтернативный режим в регистре GPIOx_MODER, вам необходимо еще указать какая именно группа периферии будет использоваться в нужных ножках (один пин могут использовать несколько периферийных блоков, но не одновременно). 32 битные регистры GPIO_AFRL и GPIO_AFRH устанавливают номер альтернативной функции на пины от 0 до 7 и от 8 до 15 соответственно любых портов. Так как одну ножку могут использовать несколько периферийных блоков, работать будет тот который будет тактироваться. Если по конструкции нужно использовать два и больше периферийных блока, у которых пины совпадают, любой периферийный блок можно вывести на другие ножки по remap.

STM32. USART

USART возможно самый простой способ передачи данных, с программной и физической точки зрения. Передатчики USART также еще ассоциируют с физическим уровнем RS-232. Принцип его действия прост : высокий уровень на линии передачи соответствует молчанию, передача начинается с перехода линии в логический ноль, это называется стартовым битом, после него принимающая система понимает что следующие логические уровни это уже - данные. Стоповый бит - последний бит соответствующий логической единице, сообщает что передача данных, отдельного байта, или просто какой-нибудь порции данных прекращена. UART передает данные не только байтами ( порциями в 8 бит ) , но есть и стандарты 5, 6, 7, и 9 бит. Все порции данных обрамлены стартовым и стоповым битами. Таким образом, для передачи/приема данных достаточно одной линии и общего нуля. Для корректной работы передатчика и приемника нужно согласовывать промежутки времени - одинаковую рабочую частоту .
Скорость передачи данных микроконтроллере STM32f407VG задается значением в регистре USART2_BRR, и вычисляется по формуле : частота микроконтроллера деленная на желаемую скорость передачи данных и деленная на 16.
Полученное число обычно не является целым, его остаток умножают на 16, и логически прибавляют его цифру к целому ( не путать с математическим).
Например: если целое 0х68 , а остаток 3, значение занесенное в регистр USART2_BRR = 0х683.
Обычно при изучении интерфейса USART, наибольшую трудность приносит понимание расчета значения для регистра скорости передачи USART2_BRR.



Адреса регистров USART:
//..........USART1......
#define RCC_UART1 *((uint32_t*)0x40023844)//RCC=0x10;
#define USART1_SR *((uint32_t*)0x40011000)
#define USART1_DR *((uint32_t*)0x40011004)
#define USART1_BRR *((uint32_t*)0x40011008)
#define USART1_CR1 *((uint32_t*)0x4001100C)
#define USART1_CR2 *((uint32_t*)0x40011010)
#define USART1_CR3 *((uint32_t*)0x40011014)
#define USART1_GTPR *((uint32_t*)0x40011018)


//.......USART2... #define RCC_UART2 *((uint32_t*)0x40023840)//RCC=0x20000;
#define USART2_SR *((uint32_t*)0x40004400)
#define USART2_DR *((uint32_t*)0x40004404)
#define USART2_BRR *((uint32_t*)0x40004408)
#define USART2_CR1 *((uint32_t*)0x4000440C)
#define USART2_CR2 *((uint32_t*)0x40004410)
#define USART2_CR3 *((uint32_t*)0x40004414)
#define USART2_GTPR *((uint32_t*)0x40004418)


//...........USART3......
#define RCC_UART3 *((uint32_t*)0x40023840)//RCC=0x40000;
#define USART3_SR *((uint32_t*)0x40004800)
#define USART3_DR *((uint32_t*)0x40004804)
#define USART3_BRR *((uint32_t*)0x40004808)
#define USART3_CR1 *((uint32_t*)0x4000480C)
#define USART3_CR2 *((uint32_t*)0x40004810)
#define USART3_CR3 *((uint32_t*)0x40004814)
#define USART3_GTPR *((uint32_t*)0x40004818)

STM32. Прерывание на кнопке

В этой статье обсудим пример применения прерывания по нажатию кнопки, на плате Discovery с микроконтроллером STM32f407VG.
Как упоминалось в статье ранее, прерывание это остановка работы основной программы и выполнение некой функции в случае появления нужного нам события. В плате Discovery с микроконтроллером STM32f407VG кнопка расположена на первой ножке порта - A. На ст. 379 документации RM0090 Reference manual указано, что ножки 1-4 (0-3 в битовом эквиваленте) находятся на нулевой линии EXTI ( EXTI0 ), и в файле StartUp который создается по умолчанию вместе с проектом в редакторе Keil, находится название соответствующей функции-обработчика для этой линии ( EXTI0_IRQHandler ) в нее мы и запишем программу, которая будет выполнятся по нажатию кнопки. В инициализации МК начинаем с подачи тактирования портов на которых светодиоды, кнопка, а также контроллера SYSCFG, в котором укажем ножки, которые мы будем "слушать". Для кнопки на нулевой ножке порта - А регистр MODER оставим по умолчанию ( ножка работает на вход) , а для портов D на которых светодиоды - конфигурация на выход.
По схеме, ножка на которой "висит" кнопка PA0, прижата к земле через сопротивление. При включении чипа, на ножке будет логический ноль, а при нажатии на кнопку - к ножке пойдет ток, но сопротивление R39 не даст создать замыкание, хоть в этот момент ножка будет и дальше прижата к нулю. Это схема подтягивания, её нужно понять , она используется всегда в электронике.



Дальше пойдут настройки Контроллера EXTI. В регистре EXTI_IMR снимем маскирование прерывания, а в регистре EXTI_RTSR установим условие прерывания - при переходе состояния ножки с нуля к единице. В SYSCFG_EXTICR1 установим пин порта которые хотим подключить к нужной линии EXTI.


  1. void EXTI0_IRQHandler (void) //название функции прерывания из файла stm32f4xx.h
  2. { uint32_t i;
  3. GPIOD_ODR=0xF000;
  4. for(i=0;i<=200000;i++);
  5. GPIOD_ODR=0;
  6. for(i=0;i<=200000;i++);
  7. if((GPIOA_IDR&0x1)==0x1)// Если кнопка будет отпущена
  8. EXTI_PR=0x1;//Произойдет выход из прерывания, функция завершит свою работу.
  9. }
  10. void func_two (void)
  11. { uint32_t i;
  12. GPIOD_ODR=0xF000;
  13. for(i=0;i<=1000000;i++);
  14. GPIOD_ODR=0;
  15. for(i=0;i<=1000000;i++);
  16. }
  17. void SystemInit(void)
  18. {
  19. }
  20. int main(void)
  21. {
  22. RCC_GPIO=0x9; //тактирование портов
  23. RCC_SYSCFG=0x4000; //тактирование SYSCFG , для выбора источника прерывания
  24. GPIOA_MODER=0xA8000000;
  25. GPIOA_PUPDR|=0x64000000;
  26. GPIOD_MODER=0x55000000;
  27. GPIOD_PUPDR=0x55000000;
  28. EXTI_IMR=0x1; //Для размаскирования соответствующих линий прерываний
  29. EXTI_RTSR=0x1; //генерация события прерывания по переходу из состояния «0» в состояние «1»
  30. SYSCFG_EXTICR1=0; //пин с какого порта подключается к определенной линии EXTI
  31. NVIC_ISER0=0x40;//из таблицы узнали номера векторов, необходимых нам прерываний. Теперь нужно записать «1» в 6 бит
  32. GPIOD_ODR=0xF000;
  33. while(1)
  34. {
  35. func_two ();
  36. }
  37. }


В данном примере, когда кнопка не нажата, будет выполнятся func_two() с относительно медленным миганием светодиодов. В момент нажатия кнопки, сработает прерывание, и основная программа остановит свое выполнение, пока не завершит работу функция EXTI0_IRQHandler ();

STM32. Контроллеры прерываний NVIC и EXTI

Прерывание - временное приостановление работы основной программы (например функции main()) для выполнения обработки какого-нибудь действия внешней функцией, что позволяет экономить ресурсы процессора не отвлекая его на проверку разных событий. Прерывание может быть также приостановлено для обработки другого прерывания если имеет высший приоритет. При срабатывании прерывания, процессор запоминает все переменные, общие параметры программы и возвращается к ним после обработки прерывания. В STM32f407VG как и у других микроконтроллерах на ядре Cortex M4 имеется встроенный векторный контроллер прерываний - Nested Vectored Interrupt Controller (NVIC). Он поддерживает до 240 динамических прерываний и 256 уровней приоритета. NVIC встроенный в ядро Cortex M4, и не является периферией, это значит что для его работы не нужно программно подключать тактирование в регистрах RCC. Для выполнения внутренних прерываний (тех которые происходят от внутренней периферии или от самого ядра) используется только NVIC, внешние прерывания обрабатываются отдельным внешним контроллером прерываний - External interrupt/event controller (EXTI). EXTI является частью NVIC .
Событие - это нечто, что может привлечь внимание контроллеров NVIC и EXTI. В работе NVIC и EXTI на физическом уровне заложены действия которые ими отслеживаются, но будет ли от них инициироваться прерывание - зависит от настроек регистров управления. Если происходит какое нибудь событие, оно может быть проверено, и если это "то что нужно" NVIC сгенерирует прерывание.
Адреса управляющих регистров прерываний:

#define NVIC_ICER0 *((uint32_t*)0xE000E180)//Interrupt Clear-Enable Registers
#define NVIC_ICER1 *((uint32_t*)0xE000E184)
#define NVIC_ICER2 *((uint32_t*)0xE000E188)

#define NVIC_ISPR0 *((uint32_t*)0xE000E200)//Interrupt Set-Pending Register
#define NVIC_ISPR1 *((uint32_t*)0xE000E204)
#define NVIC_ISPR2 *((uint32_t*)0xE000E208)

#define NVIC_ICPR0 *((uint32_t*)0xE000E280)//Interrupt Clear-Pending Register
#define NVIC_ICPR1 *((uint32_t*)0xE000E284)
#define NVIC_ICPR2 *((uint32_t*)0xE000E288)

#define NVIC_IABR0 *((uint32_t*)0xE000E300)//Active Bit Register
#define NVIC_IABR1 *((uint32_t*)0xE000E300)
#define NVIC_IABR2 *((uint32_t*)0xE000E300)

#define NVIC_IPR0 *((uint32_t*)0xE000E400)//Interrupt Priority Registers
#define NVIC_IPR1 *((uint32_t*)0xE000E404)
#define NVIC_IPR2 *((uint32_t*)0xE000E408)
#define NVIC_IPR3 *((uint32_t*)0xE000E40C)
#define NVIC_IPR4 *((uint32_t*)0xE000E410)
#define NVIC_IPR5 *((uint32_t*)0xE000E414)
#define NVIC_IPR6 *((uint32_t*)0xE000E418)
#define NVIC_IPR7 *((uint32_t*)0xE000E41C)
//.......................
#define CRC_DR *((uint32_t*)0x40023000)
#define CRC_IDR *((uint32_t*)0x40023004)
#define CRC_CR *((uint32_t*)0x40023008)
//........................
#define RCC_SYSCFG *((uint32_t*)0x40023844)
#define SYSCFG_MEMRMP *((uint32_t*)0x40013800)
#define SYSCFG_PMC *((uint32_t*)0x40013804)
#define SYSCFG_EXTICR1 *((uint32_t*)0x40013808)
#define SYSCFG_EXTICR2 *((uint32_t*)0x4001380C)
#define SYSCFG_EXTICR3 *((uint32_t*)0x40013810)
#define SYSCFG_EXTICR4 *((uint32_t*)0x40013814)
#define SYSCFG_CMPCR *((uint32_t*)0x40013820)
//..............EXTI...........
#define EXTI_IMR *((uint32_t*)0x40013C00)
#define EXTI_EMR *((uint32_t*)0x40013C04)
#define EXTI_RTSR *((uint32_t*)0x40013C08)
#define EXTI_FTSR *((uint32_t*)0x40013C0C)
#define EXTI_SWIER *((uint32_t*)0x40013C10)
#define EXTI_PR *((uint32_t*)0x40013C14)

STM32. Мигание светодиодов

В прошлой статье обсуждалась адресация регистров и их предназначение микроконтроллера STM32F407VG. Код, который зажигает и гасит светодиоды начинается с названия файла общих настроек , а через #define мы указываем более читаемый вид указателям на регистры управления. В случае если таких регистров в программе будет много, целесообразно вынести этот код в отдельный файл, и подключить его следующим видом:

#include "имя.h"

Где имя в кавычках обозначает что файл не внутренний а внешний(хотя поместив его в нужные папки редактора Keil он также становится внутренним.)
Далее следует функция SystemInit(), она заполняет первоначальными настройками STM32F407VG из файла stm32f4xx.h. Без использования разных библиотек - ее присутствие обязательно.
Функция main запускает пользовательский код, прежде всего идут настройки инициализации уже нужных вам блоков, которые достаточно для запуска программы на первом этапе. В этом примере инициализация начинается с включения тактирования ножек нужного нам порта GPIOD, установкой третьего бита в регистре RCC_GPIO. Далее следуют минимальные нужные настройки регистров управления портом GPIOD, включающие биты для работы ножек в режиме вывода, а также прижатием их к положительному питанию.
Так как программа микроконтроллера STM32F407VG работает в вечном режиме, создаем постоянное условие true в цикле while(1) занося в него единичку.

  1. void SystemInit(void)
  2. {
  3. }
  4. int main(void){
  5. RCC_GPIO|=0x8;
  6. GPIOD_MODER=0x55000000;
  7. GPIOD_PUPDR=0x55000000;
  8. while(1)
  9. {
  10. GPIOD_ODR=0xA000;
  11. for(i=0;i<1000000;i++){};
  12. GPIOD_ODR=0;
  13. for(i=0;i<1000000;i++){};
  14. }
  15. }

STM32. Регистры портов GPIO

В этой статье перейдем к разбору действий для управления портами ввода/вывода. В технической документации к плате Discovery с чипом STM32f407VG указано, что светодиоды подключены к порту GPIOD и ножкам 12, 13, 14, 15. Смотрим в RM0090 Reference manual, и видим, что регистры управления порта D (GPIOD) занимают пространство в памяти, начиная с адреса 0x4002 0C00. Дальше в документации идет описания регистров управления GPIOx (где "х" имя порта:A,B,C...). Первый регистр - GPIOx_MODER, адрес смещения - ноль, то есть он первый в блоке и находится по адресу 0x40020C00. Он отвечает за направление тока : на вход , выход , или режим альтернативной функции ( то есть к ножкам можно подключать разные внутренние блоки/интерфейсы передачи\приема данных, а не только использовать для вкл/выкл +3V). Так как нам нужно зажечь светодиод, устанавливаем режим вывода ножек 12,13,14,15 . Записываем "01" в каждую пару битов нужных ножек, и записываем получившееся число в указатель на регистр GPIOD moder:




*((uint32_t*)0x40020C00)=0х55000000;
 
 
 
 
 
Смотрим дальше, следующий регистр GPIOxOTYPER, можно оставить по умолчанию. GPIOxSPEEDR , скорость считывания состояния ножки, можем оставить по умолчанию. GPIOxPUPDR-регистр позволяет "прижать" ножку к положительному или отрицательному значению тока.
GPIOx_IDR - Регистр только для чтения, указывает на логическое значение на соответствующей ножке. Можно его использовать, например, если нужно узнать нажата ли кнопка, или пришел ли какой нибудь сигнал на ножку.
GPIOx_ODR - регистр для чтения и записи. Запись "1" в нужный бит подаст логическую единицу на соответствующую ножку. Запись "0" - сбросит бит, и установит на ножке логический ноль.
GPIOx_BSRR - Регистр устанавливает логический ноль или единицу на ножках. в отличии от регистра GPIOx_ODR, значение которого нужно переписывать целиком ради установки отдельного бита, GPIOx_BSRR - можно точечно устанавливать соответствующие биты регистра GPIOx_ODR, не перезаписывая его значения целиком. Установка "1" в биты 0-15 , установит биты 0-15 в GPIOx_ODR. Установка "1" в биты 16-31 установит "0" в биты 0-15 в GPIOx_ODR. запись "0" в регистр GPIOx_BSRR не дает никакого эффекта.
GPIOx_LCKR - регистр устанавливающий блокировку на изменение настроек регистров портов.
GPIOx_AFRL - Регистр альтернативных функций ножек 0-7.
GPIOx_AFRH - Регистр альтернативных функций ножек 7-15.

Адреса регистров GPIO

#define RCC_GPIO *((uint32_t*)0x40023830) //.................
#define GPIOA_MODER *((uint32_t*)0x40020000)
#define GPIOA_OTYPER *((uint32_t*)0x40020004)
#define GPIOA_OSPEEDR *((uint32_t*)0x40020008)
#define GPIOA_IDR *((uint32_t*)0x40020010)
#define GPIOA_PUPDR *((uint32_t*)0x4002000C)
#define GPIOA_ODR *((uint32_t*)0x0x40020014)
#define GPIOA_BSRR *((uint32_t*)0x40020018)
#define GPIOA_AFRL *((uint32_t*)0x40020020)
#define GPIOA_AFRH *((uint32_t*)0x40020024)
//.................
#define GPIOB_MODER *((uint32_t*)0x40020400)
#define GPIOB_OTYPER *((uint32_t*)0x40020404)
#define GPIOB_OSPEEDR *((uint32_t*)0x40020408)
#define GPIOB_IDR *((uint32_t*)0x40020410)
#define GPIOB_PUPDR *((uint32_t*)0x4002040C)
#define GPIOB_ODR *((uint32_t*)0x40020414)
#define GPIOB_BSRR *((uint32_t*)0x40020418)
#define GPIOB_AFRL *((uint32_t*)0x40020420)
#define GPIOB_AFRH *((uint32_t*)0x40020424)
//................
#define GPIOC_MODER *((uint32_t*)0x40020800)
#define GPIOC_OTYPER *((uint32_t*)0x40020804)
#define GPIOC_OSPEEDR *((uint32_t*)0x40020808)
#define GPIOC_IDR *((uint32_t*)0x40020810)
#define GPIOC_PUPDR *((uint32_t*)0x4002080C)
#define GPIOC_ODR *((uint32_t*)0x40020814)
#define GPIOC_BSRR *((uint32_t*)0x40020818)
#define GPIOC_AFRL *((uint32_t*)0x40020820)
#define GPIOC_AFRH *((uint32_t*)0x40020824)
//.................
#define GPIOD_MODER *((uint32_t*)0x40020C00)
#define GPIOD_OTYPER *((uint32_t*)0x40020C04)
#define GPIOD_OSPEEDR *((uint32_t*)0x40020C08)
#define GPIOD_IDR *((uint32_t*)0x40020C10)
#define GPIOD_PUPDR *((uint32_t*)0x40020C0C)
#define GPIOD_ODR *((uint32_t*)0x40020C14)
#define GPIOD_BSRR *((uint32_t*)0x40020C18)
#define GPIOD_AFRL *((uint32_t*)0x40020C20)
#define GPIOD_AFRH *((uint32_t*)0x40020C24)
//................
#define GPIOE_MODER *((uint32_t*)0x40021000)
#define GPIOE_OTYPER *((uint32_t*)0x40021004)
#define GPIOE_OSPEEDR *((uint32_t*)0x40021008)
#define GPIOE_IDR *((uint32_t*)0x40021010)
#define GPIOE_PUPDR *((uint32_t*)0x4002100C)
#define GPIOE_ODR *((uint32_t*)0x40021014)
#define GPIOE_BSRR *((uint32_t*)0x40021018)
#define GPIOE_AFRL *((uint32_t*)0x40021020)
#define GPIOE_AFRH *((uint32_t*)0x40021024)
 

STM32. Адресация регистров

Прежде чем приступить к написанию программы для микроконтроллера, нужно подключить тактирование нужных блоков, за это отвечает регистр RCC, находим его в документации, и ищем биты , установка которых включает тактирование для нужных портов. Находим регистр RCCAHB1ENR который тактирует порты и некоторые другие блоки. Регистр RCCAHB1ENR как мы видим имеет адрес смещения 0x30. Сам блок тактирования RCC занимает в адресном пространстве некоторый диапазон памяти для своих регистров управления, поэтому мы ищем адрес первого регистра RCC и складываем его с адресом смещения того регистра который нам нужен:


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



#define RCC_GPIO *((uint32_t*)0x40023830)
И для присваивания числа, уже можно записывать любое удобное имя: RCC_GPIO=0x3, RCC_PORTS=0b1000; ...

Внимание! При установлении значений в регистры, нужно смотреть, нет ли каких-нибудь значений по умолчанию в каждом регистре. Если регистр имеет значение сброса, следующая установка значений происходит через оператор "ИЛИ":



Из документации видно, что порты GPIOA и GPIOB уже имеют значения по умолчанию, поэтому установка значений будет проходить следующим образом:



GPIOA_MODER|=0x80;
GPIOB_MODER|=0x80;