STM32 HAL Kütüphanesi ile UART Protokolünü Kullanma
Önceki yazımızda CubeMX ile ön ayarları oluşturmuştuk ve projemizi hazırlamıştık. Bu projede örnek bir kod olarak 0x01 değerini UART protokolü ile lojik analizöre gönderdim ve bunun grafiğini elde ettim. Bu grafiği açıklayarak yazımıza başlayalım.
Burada programda sürekli olarak gönderdiğim verinin bir kesitini görmekteyiz. 1 ile işaretlediğim kısım START biti olup iletişimi başlatır. 2 numaradan itibaren devam eden kısım veri kısmı olup 8 bitlik veriyi sol hizalı şekilde göndermektedir. Benim gönderdiğim veri 0x01 olduğu için 10000000 şeklinde yollamıştır. Bunu grafikten görebilirsiniz. 3 numaralı bit ise STOP biti olup verinin tamamlandığını haber verir. START -> Veri -> STOP şeklinde devam eden bu veri aktarımı en yapılabilecek en basit asenkron seri veri aktarımıdır. Eğer istenirse eşlik biti gibi çeşitli özellikler burada eklenebilir. Bu kutucuk aynı zamanda frame yani çerçeve adıyla da adlandırılmaktadır. Bunun zamanlamasını ise önceden belirlediğimiz baud değeri olan 9600 ile belirliyorduk. Yani bir saniye 9600’e bölünür ve elde edilen değer aralığında ölçüm yapılır. Burada bunu anladığımıza göre şimdi bu sinyali nasıl ürettiğimize bir bakalım.
Önceki makalede ürettiğimiz projenin başlık dosyalarına baktığımızda stm32f3xx_hal_conf.h dosyasında şöyle bir değişiklik olduğunu görüyoruz.
1 2 3 4 5 6 7 |
/*#define HAL_RTC_MODULE_ENABLED */ /*#define HAL_SPI_MODULE_ENABLED */ /*#define HAL_TIM_MODULE_ENABLED */ #define HAL_UART_MODULE_ENABLED /*#define HAL_USART_MODULE_ENABLED */ /*#define HAL_IRDA_MODULE_ENABLED */ /*#define HAL_SMARTCARD_MODULE_ENABLED */ |
Bunu daha önceden size anlatmıştık. Kullanmak istediğimiz HAL sürücüsünü buradan açıklama işaretlerini kaldırarak etkin hale getiriyoruz. Bu tanımlama ile dosyanın aşağısındaki koşullu tanım yapısı işletilmektedir.
1 2 3 |
#ifdef HAL_UART_MODULE_ENABLED #include "stm32f3xx_hal_uart.h" #endif /* HAL_UART_MODULE_ENABLED */ |
Kısacası burada yaptığımız iş stm32f3xx_hal_uart.h dosyasını projemize dahil etmektir. HAL kütüphanesinde her sürücünün ayrı bir dosyası olduğunu size söylemiştik. Başlık dosyalarında daha fazla bir farklılık görmediğimiz için şimdi inceleyeceğimiz ikinci dosya olan “src” klasöründeki stm32f3xx_hal_msp.c dosyasına bakalım.
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 |
void HAL_UART_MspInit(UART_HandleTypeDef* huart) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(huart->Instance==USART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* Peripheral clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); /**USART1 GPIO Configuration PC4 ------> USART1_TX PC5 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } } |
Burada HAL kütüphanesinde yaptığımız UART ayarları ile ilgili ön ayarlar yer almaktadır. Bunların USART sürücüsünün veri tipleri ile alakalı olmadığını görmüş olmanız lazımdır. Öncelikle huart adındaki yapının Instance yani eşleştirildiği birimin adının USART1 olup olmadığına dair bir denetleme kodu görülmektedir. Bunu şuradan görebiliriz.
1 |
if(huart->Instance==USART1) |
Şimdilik bunun hakkında bir fikrimiz yok çünkü main.c içerisindeki yapıyı incelemedik. Fakat ADC biriminde gördüğümüz gibi bu yapı kütüphane ile alakalı olup eğer bu ayarı yaptıysak aşağıdaki diğer gerekli komutları çalıştırmak üzere buraya koyulmuştur.
1 2 3 |
__HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); |
Burada USART1 biriminin saatini etkinleştirdiğimiz gibi GPIOC biriminin saatini de etkinleştiriyoruz. Çünkü TX ve RX ayakları GPIOC birimine bağlanmıştır. Sonrasında ise yapacağımız GPIO sürücüsünü kullanarak bu ayak ayarlarını yapmak.
1 2 3 4 5 6 |
GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); |
Görüldüğü gibi GPIO_InitStruct.Mode kısmında GPIO_MODE_AF_PP olarak ayak için alternatif fonksiyon seçeneğini seçtik. Bu alternatif fonksiyonun ne olacağını ise aşağıdaki GPIO_InitStruct.Alternate yapısına eklenen değer ile görmekteyiz. GPIO_AF7_USART1 değeri alternatif fonksiyon olarak USART1 birimini kullanın demektir. Her ayak başına düşen alternatif fonksiyonların ne olacağını ise datasheetten öğrenebiliriz. Buna benzer kodları önceden ayrıntısıyla açıkladığımız için çok üzerinde durmuyoruz. Şimdi ise main.c dosyasına bakalım ve buradaki komutları inceleyelim.
1 |
UART_HandleTypeDef huart1; |
Size kütüphane kılavuzunu incelerken öncelikle UART_HandleTypeDef adında bir yapı tipinin tanımlanması gerektiği ve bunlara gerekli verilerin yazılmasından bahsetmiştik. Öncelikle böyle bir yapı tipinde değişken tanımlıyoruz ve bunu program huart1 olarak adlandırıyor. Biz programdan bağımsız olduğumuzda istediğimiz bir adı koyabiliriz. Kodun devamında bizim USART1 birimi için ayrı bir saat ayarlaması yapıldığını görmekteyiz.
1 2 3 |
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1; PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) |
Burada PeriphClkInit atlı RCCEx sürücüsüne ait yapıya saat ayarı değeri yüklenip ayarlanmaktadır. Kısacası öncelikle ayarlayacağımız saat RCC_PERIPTHCLK_USART1 yani USART1 biriminin saati olarak seçilmiştir. Sonrasında ise bu hangi saat kaynağından besleneceği RCC_USART1CLKSOURCE_PCLK2 değeri ile belirlenmiştir. USART1 bu durumda PCLK2 saatinden beslenmektedir. Şimdi ise asıl incelememiz gereken yere geliyoruz. MX_USART1_UART_Init fonksiyonu içerisinde huart1 adında tanımladığımız sürücü yapı tipine ayar verilerini yüklüyoruz. Bunun için şöyle bir kod bloku kullanılmıştır.
1 2 3 4 5 6 7 8 9 10 11 |
huart1.Instance = USART1; huart1.Init.BaudRate = 9600; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_Init(&huart1) != HAL_OK) |
Burada programda yaptığımız ayarların aynısını görmemiz mümkündür. Öncelikle Instance değerine birimin adı atanmıştır. USART1 demek ile USART1 birimindeki yazmaçlara erişim sağlamış oluruz. Bu atadığımız değerler de USART1 yapısı içerisindeki yazmaçlara yüklenmektedir. USART1’in yazmaçlarına erişmek için ok operatörünü (->) kullandığımızı unutmayalım. Kısacası USART1 burada karşımıza içinde yazmaçları bulunduran bir yapı olarak çıkmaktadır. Bu UART1 yapısı donanım olarak UART1 birimini temsil etmektedir. Bu yapının içerisinde hangi yazmaçların olduğunu da kaynak kodunu inceleyerek görebiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
typedef struct { __IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x00 */ __IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x04 */ __IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x08 */ __IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x0C */ __IO uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x10 */ __IO uint32_t RTOR; /*!< USART Receiver Time Out register, Address offset: 0x14 */ __IO uint32_t RQR; /*!< USART Request register, Address offset: 0x18 */ __IO uint32_t ISR; /*!< USART Interrupt and status register, Address offset: 0x1C */ __IO uint32_t ICR; /*!< USART Interrupt flag Clear register, Address offset: 0x20 */ __IO uint16_t RDR; /*!< USART Receive Data register, Address offset: 0x24 */ uint16_t RESERVED1; /*!< Reserved, 0x26 */ __IO uint16_t TDR; /*!< USART Transmit Data register, Address offset: 0x28 */ uint16_t RESERVED2; /*!< Reserved, 0x2A */ } USART_TypeDef; |
Bu yazmaçların ve donanımı temsil eden yapıların HAL kütüphanesinden bağımsız halde olduklarını söyleyelim. İstenirse HAL hiç kullanılmadan program yazılabilir. Bu tanımlamalar stm32f303xc.h dosyasında mevcuttur. Biz yazmaç tabanlı program yazarken de bu sayfayı referans olarak kullanabiliriz.
Kodun devamına baktığımızda huart1.Init.xxxx diye devam ettiğini görüyoruz. Burada huart1 yapısının içerisinde Init adlı bir yapının değişkenlerine erişim olduğunu görebiliriz. Init yapısı UART_InitTypeDef tipinde olup çeşitli ayar verilerini içerisinde bulundurur. Şimdi veri göndermek için kullandığımız fonksiyonu inceleyelim.
1 2 |
char ch = 0x01; HAL_UART_Transmit(&huart1,(uint8_t *)&ch, 1, 0xFFFF); |
Bu fonksiyon başta anlaması zor gibidir fakat aldığı parametreler oldukça basittir. Fonksiyonun prototipini inceleyerek daha iyi açıklamaya çalışalım.
1 |
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) |
Görüldüğü gibi önce tanımladığımız UART yapısı, sonra veri, sonra verinin boyutu ve en sonunda da zaman aşımı süresi belirtilmektedir. Bizim ch verisi burada işaretçi olarak alınmaktadır. Aynı şekilde UART_HandleTypeDef verimiz de işaretçi olarak alınır. O yüzden adresleme operatörü ile (&) bu verilerin değerini aktarmamız gereklidir. Öncelikle ch adında ve 0x01 değerinde bir karakter verisi oluşturuyoruz ve bunu adresleyerek işaretçiye çevirip argüman olarak aktarıyoruz. Sonrasında ise verinin uzunluğu ve zaman aşımı süresini yazdıktan sonra fonksiyonumuz tamamlanmış oluyor.
Bilmemiz gereken önemli bir nokta ise mikrodenetleyici 3.3V ile çalışmakta ve 5V seri iletişim ekipmanları ile uyumda sıkıntı yaşayabilmektedir. Ayrıca bilgisayara doğrudan seri iletişimle bağlayacağımız bir USB portu bulunmamaktadır. Ben fazla uğraşmak istemeyip lojik analizörle bir analiz yaptım. Daha sonra UART konusunu daha ayrıntılı ele almak üzere burada bırakıyoruz.
Bizi Facebook grubumuzda takip etmeyi unutmayın. Bilgili ve öğrenmeye hevesli bir topluluk oluşturmak istiyoruz.
https://www.facebook.com/groups/1233336523490761/
UYARI!!
Bu sitede yayınlanan yazılar orjinal içerik olup faydalanılan kaynaklar belirtilmiştir. Yazarın izni olmaksızın tamamen alıntı yapılamaz, kopyalanamaz. Kaynak göstermek kaydıyla (Yazının adı, yazar adı ve link) kısmen alıntı yapılabilir.
Merhaba,
Öncelikle ayrıntılı açıklamanız için teşekkürler.
UART için MspInit işlevini çalışma zamanında çağırmak mümkün müdür? Amacım çalışma zamanında UART1 (ör) için atanmış default (ör. PD1) ve alternate (PA1) pinlerden veri akışını (ör. sadece Rx) yönetmek.
Daha açık haliyle UART1 önce default pin (PD1) üzerinden veri alacak. Çalışma zamanında duruma göre alternate pin (PA1) den veri almaya başlayacak.
HAL_UART_MspInit ve HAL_UART_MspDeInit işlevlerinde ki içeriği özelleştirerek çağırdım. Veri almayı başaramadım.
Mümkün müdür? Herhangi bir öneriniz olur mu?
Değerli vaktiniz için teşekkürler