日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

STM32关于使用定时器来实现串口通信的整活实验

發(fā)布時(shí)間:2023/12/8 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 STM32关于使用定时器来实现串口通信的整活实验 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

  • 一、整活說明
  • 二、原理簡(jiǎn)介
    • 先說接收
    • 發(fā)送數(shù)據(jù)
    • 優(yōu)化
  • 三、實(shí)驗(yàn)條件和目標(biāo)
    • 條件
    • 目標(biāo)
  • 四、掉頭發(fā)環(huán)節(jié)
    • 1.定義串口類
    • 2.定義簡(jiǎn)單宏
    • 3.定義私有數(shù)據(jù)
    • 4.init 函數(shù)
    • 5.其他接口函數(shù)
    • 6.中斷函數(shù)
    • 7.接口導(dǎo)出
  • 五、驗(yàn)證

一、整活說明

在幫別人做項(xiàng)目的時(shí)候,寫著代碼唱著歌,突然就遇到硬件工程師把線連錯(cuò)了,串口模塊連接的不是UART引腳,項(xiàng)目就這樣暫停了嗎?這能忍嗎????這不能忍啊!作為一個(gè)資深(咸魚)軟件工程師務(wù)必不能被這種小事絆住手腳,項(xiàng)目還是要進(jìn)行下去,所以就有了這次整活。

二、原理簡(jiǎn)介

串口原理就不重復(fù)了,直接網(wǎng)上隨便一篇文章就能解決問題。在此說明使用定時(shí)器實(shí)現(xiàn)串口的理論依據(jù),本篇所說的串口參數(shù)始終為8位數(shù)據(jù)位、1位停止位、無校驗(yàn),并稱傳輸一個(gè)bit所需的時(shí)間為一個(gè)tick,1 tick=(1000000/波特率)us。

先說接收

串口通信始終以一個(gè)下降沿開始維持一個(gè)tick的時(shí)間,然后開始以低位在前的形式發(fā)送數(shù)據(jù),根據(jù)數(shù)據(jù)位來控制引腳電平,持續(xù)8 tick,最后是1 tick的高電平結(jié)束。由此可得出一個(gè)方案,中斷設(shè)置為下降沿觸發(fā),然后打開定時(shí)器,每隔 1 tick 進(jìn)入一次中斷判斷接收引腳的電平并存儲(chǔ),經(jīng)過8 tick之后接收完畢,關(guān)閉定時(shí)器。

發(fā)送數(shù)據(jù)

由于串口通信的開始位和結(jié)束位都為 1 tick ,所以可以把這兩個(gè)過程看作各是1bit數(shù)據(jù)。最終要發(fā)送的數(shù)據(jù)被組合為 tdr=(data<<1)|0x40,然后打開定時(shí)器,每隔 1 tick進(jìn)入一次中斷根據(jù)tdr最低位的值控制發(fā)送引腳的電平,經(jīng)過10 tick之后發(fā)送完畢,關(guān)閉定時(shí)器。

優(yōu)化

按照上面的思路,要實(shí)現(xiàn)一個(gè)雙工串口豈不是要用2個(gè)定時(shí)器?這樣做可以是可以,但是太大材小用了。作為一個(gè)愛兵如子的軟件工程師,每個(gè)單片機(jī)外設(shè)都是極其重要的,如果一個(gè)外設(shè)不能發(fā)揮出它應(yīng)有的光芒,想必作為一個(gè)外設(shè),它也是不高興的吧。實(shí)際實(shí)現(xiàn)用的是定時(shí)器的比較輸出功能,理論上一個(gè)4通道定時(shí)器可以實(shí)現(xiàn)兩路雙工串口,只能說定時(shí)器YYSD,單片機(jī)不能沒有定時(shí)器,就像西方不能沒有耶路撒冷!
另外,由于串口通信需要頻繁進(jìn)中斷并在中斷中執(zhí)行代碼,為節(jié)省時(shí)間實(shí)際實(shí)現(xiàn)使用宏和寄存器的形式編程。

三、實(shí)驗(yàn)條件和目標(biāo)

條件

本實(shí)驗(yàn)使用的硬件使用情況如下:

項(xiàng)目Value
單片機(jī)STM32F407VGTx
UART_TXPB10
UART_RXPB11
定時(shí)器TIM2
定時(shí)器通道1用于發(fā)送
定時(shí)器通道2用于接收
外部中斷用于觸發(fā)接收

你說PB10和PB11就是USART3的TX和RX腳啊,我有串口,但是我不用,哎嘿,就是玩~
都用串口了還叫整活嗎?明顯不叫啊,沒這個(gè)理是不是?

目標(biāo)

項(xiàng)目Value
參數(shù)1位停止位,無校驗(yàn)
波特率可配置
全雙工
數(shù)據(jù)發(fā)送非阻塞,中斷發(fā)送
數(shù)據(jù)接收緩沖區(qū),中斷接收

總之正常串口有的都要有,用戶體驗(yàn)上不能有區(qū)別。

四、掉頭發(fā)環(huán)節(jié)

新建文件 dev_vuart.h,dev_vuart.c

1.定義串口類

頭文件 dev_vuart.h ,添加內(nèi)容如下:

#ifndef dev_vuart_h__ #define dev_vuart_h__#include "stdint.h"typedef struct{int (*init)(void);int (*write)(const uint8_t *d,int len);int (*read)(uint8_t *d,int len);void *(*set_irqfun)(void (*fun)(uint8_t d,void *context),void *context); }vuart_typedef;vuart_typedef *vuart(void);#endif

和普通串口接口相同,分為初始化,發(fā)送,接收,定義中斷函數(shù)幾個(gè)函數(shù)。

2.定義簡(jiǎn)單宏

串口需要頻繁進(jìn)入中斷并在中斷中處理數(shù)據(jù),所以不宜使用庫函數(shù)編程,定義簡(jiǎn)單宏如下,多數(shù)宏是根據(jù)stm32 hal庫修改而來。

#define __MY_TIM TIM2 #define TIM_CNT_MAX 0xffff/*r{ 定義定時(shí)器運(yùn)行頻率,單位Mhz }c*/ #define TIM_FREQUENCY (84) /*r{ 定義波特率 }c*/ #define VUART_RATE (115200)#define VUART_BIT_TICK (TIM_FREQUENCY*(1000000/VUART_RATE))#define __MY_TIM_ENABLE(__HANDLE__) ((__HANDLE__)->CR1|=(TIM_CR1_CEN))#define __MY_TIM_DISABLE(__HANDLE__) \do { \if (((__HANDLE__)->CCER & TIM_CCER_CCxE_MASK) == 0UL) \{ \if(((__HANDLE__)->CCER & TIM_CCER_CCxNE_MASK) == 0UL) \{ \(__HANDLE__)->CR1 &= ~(TIM_CR1_CEN); \} \} \} while(0)#define __MY_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->DIER |= (__INTERRUPT__))#define __MY_TIM_DISABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->DIER &= ~(__INTERRUPT__))#define __MY_TIM_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->SR &(__FLAG__)) == (__FLAG__))#define __MY_TIM_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->SR = ~(__FLAG__))#define __MY_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->SR = ~(__INTERRUPT__))#define __MY_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)->CNT)#define __MY_TIM_GET_IT_SOURCE(__HANDLE__, __INTERRUPT__) (((__HANDLE__)->DIER & (__INTERRUPT__))== (__INTERRUPT__))/*r{ 設(shè)置通道寄存器值,__CHANNEL__取值0~3 }c*/ #define __MY_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \(*((&(__HANDLE__)->CCR1)+(__CHANNEL__)) = (__COMPARE__))/*r{ 獲取通道寄存器值,__CHANNEL__取值0~3 }c*/ #define __MY_TIM_GET_COMPARE(__HANDLE__, __CHANNEL__) \(*((&(__HANDLE__)->CCR1)+(__CHANNEL__)))#define __MY_SET_PIN(__GPIO__,__PIN__) (__GPIO__->BSRR = __PIN__) #define __MY_RESET_PIN(__GPIO__,__PIN__) (__GPIO__->BSRR = (uint32_t)__PIN__ << 16U)#define __MY_READ_PIN(__GPIO__,__PIN__) (__GPIO__->IDR & __PIN__)/*r{ 打開外部中斷;__INTERRUPT__取值0~22 }c*/ #define __MY_EXIT_ENABLE_IT(__INTERRUPT__) (EXTI->IMR |= 1<<(__INTERRUPT__))#define __MY_EXIT_DISABLE_IT(__INTERRUPT__) (EXTI->IMR &= ~(1<<(__INTERRUPT__)))

3.定義私有數(shù)據(jù)

typedef struct {int inited;// 是否初始化uint16_t tdr;// 模擬發(fā)送寄存器uint16_t rdr;// 模擬接收寄存器uint8_t tdr_left_bit;// 發(fā)送寄存器剩余bit數(shù)uint8_t rdr_left_bit;// 接收寄存器剩余bit數(shù)data_buff sbuff;// 發(fā)送緩沖區(qū)data_buff rbuff;// 接收緩沖區(qū)void (*irqfun)(uint8_t d,void *context);// 用戶中斷回調(diào)void *context;int in_send;// 正在發(fā)送 }self_data;static self_data g_data;

以及一些操作宏:

// 開始接收 #define __VUART_RX_DR()\{g_data.rdr=0;\g_data.rdr_left_bit=8;\set_channel_after_tick(1,VUART_BIT_TICK*3/2);}// 接收一個(gè)bit #define __VUART_RX_BIT()\{\if(g_data.rdr_left_bit>0)\{\if(__MY_READ_PIN(GPIOB,GPIO_PIN_11))\{\g_data.rdr|=1<<(8-g_data.rdr_left_bit);\}\g_data.rdr_left_bit--;\}\set_channel_after_tick(1,VUART_BIT_TICK);\} // 開始發(fā)送 #define __VUART_SET_DR(d)\{g_data.tdr=((uint16_t)(d)<<1)|0x0200;\g_data.tdr_left_bit=1+8+1;\set_channel_after_tick(0,VUART_BIT_TICK);}// 發(fā)送一個(gè)bit #define __VUART_TX_BIT()\{\if(g_data.tdr_left_bit)\{\if(g_data.tdr&0x0001)\__MY_SET_PIN(GPIOB,GPIO_PIN_10);\else\__MY_RESET_PIN(GPIOB,GPIO_PIN_10);\g_data.tdr>>=1;g_data.tdr_left_bit--;\}\set_channel_after_tick(0,VUART_BIT_TICK);\}

函數(shù) set_channel_after_tick 的作用是設(shè)置通道在指定tick之后產(chǎn)生中斷。

/**r{* fun: 把比較寄存器值設(shè)置為tick時(shí)間之后* par: channel:0~3* return: }c**/ static void set_channel_after_tick(int channel,int tick) {int t1=__MY_TIM_GET_COUNTER(__MY_TIM);if(t1+tick>TIM_CNT_MAX)__MY_TIM_SET_COMPARE(__MY_TIM,channel,t1+tick-TIM_CNT_MAX);else__MY_TIM_SET_COMPARE(__MY_TIM,channel,t1+tick); }

4.init 函數(shù)

init函數(shù)作用如下:
1.初始化緩沖區(qū)
2.初始化定時(shí)器并打開通道1,2
3.初始化引腳PB10為輸出
4.初始化引腳PB11為下降沿觸發(fā)中斷
5.打開TIM和EXIT的全局中斷

static TIM_HandleTypeDef g_tim; static int init(void) {if(g_data.inited) return 0;buff_init(&g_data.sbuff,200,0,0,0);buff_init(&g_data.rbuff,200,0,0,0);TIM_Base_InitTypeDef *init=&g_tim.Init;g_tim.Instance=__MY_TIM;init->Prescaler=0;init->CounterMode=TIM_COUNTERMODE_UP;init->Period=TIM_CNT_MAX;init->ClockDivision=TIM_CLOCKDIVISION_DIV1;init->RepetitionCounter=0;init->AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE;__HAL_RCC_TIM2_CLK_ENABLE();HAL_TIM_Base_Init(&g_tim);__MY_TIM_ENABLE(__MY_TIM);HAL_NVIC_SetPriority(TIM2_IRQn, 3, 1);HAL_NVIC_EnableIRQ(TIM2_IRQn);GPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_GPIOB_CLK_ENABLE();/*r{ PB10->TX;PB11->RX }c*/GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_11;GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);__MY_SET_PIN(GPIOB,GPIO_PIN_10);__MY_SET_PIN(GPIOB,GPIO_PIN_11);HAL_NVIC_SetPriority(EXTI15_10_IRQn, 3, 1);HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);/*r{ 打開通道0,1 }c*/__MY_TIM->CCER=0x0011;g_data.inited=1;return 0; }

5.其他接口函數(shù)

write函數(shù)與普通串口的中斷發(fā)送操作類似,先把數(shù)據(jù)全部存入緩沖區(qū),設(shè)置發(fā)送寄存器,然后打開發(fā)送中斷即可;
read函數(shù)只需讀取緩沖區(qū)即可,與普通串口相同;
set_irqfun函數(shù)設(shè)置中斷回調(diào),也與普通串口相同。

static int write(const uint8_t *d,int len) {uint8_t res;buff_save_bytes(&g_data.sbuff,d,len);if(g_data.in_send==0){g_data.in_send=1;if(buff_read_byte(&g_data.sbuff,&res)==0){__VUART_SET_DR(res);__MY_TIM_ENABLE_IT(__MY_TIM,TIM_IT_CC1);}}return len; } static int read(uint8_t *d,int len) {if(buff_read_bytes(&g_data.rbuff,d,len)==0)return len;elsereturn 0; } static void *set_irqfun(void (*fun)(uint8_t d,void *context),void *context) {void *r=g_data.irqfun;g_data.irqfun=fun;g_data.context=context;return r; }

6.中斷函數(shù)

串口發(fā)送只需要用到定時(shí)器中斷,而接收則需要同時(shí)用到外部中斷和定時(shí)器中斷。
發(fā)送中斷由write函數(shù)調(diào)用 __VUART_SET_DR ,__MY_TIM_ENABLE_IT(__MY_TIM,TIM_IT_CC1) 開啟,然后在定時(shí)器中斷中依次逐bit發(fā)送,1 byte發(fā)送完成之后檢測(cè)發(fā)送緩沖區(qū)中是否還有數(shù)據(jù),如有,則再次調(diào)用 __VUART_SET_DR 重復(fù)以上流程;如沒有數(shù)據(jù),則調(diào)用 __MY_TIM_DISABLE_IT(__MY_TIM,TIM_IT_CC1) 關(guān)閉發(fā)送中斷,此時(shí)發(fā)送結(jié)束;
接收中斷由EXTI觸發(fā),一旦檢測(cè)到下降沿,在EXTI中斷服務(wù)函數(shù)中調(diào)用 __VUART_RX_DR ,__MY_TIM_ENABLE_IT(__MY_TIM,TIM_IT_CC2),打開接收中斷,調(diào)用 __MY_EXIT_DISABLE_IT 關(guān)閉EXTI中斷防止在數(shù)據(jù)接收過程中重復(fù)觸發(fā),然后在定時(shí)器中斷中依次逐bit接收,1 byte接收完成后存入接收緩沖區(qū),然后調(diào)用 __MY_TIM_DISABLE_IT(__MY_TIM,TIM_IT_CC2) 關(guān)閉接收中斷,調(diào)用 __MY_EXIT_ENABLE_IT 打開EXTI中斷方便下次接收。

void TIM2_IRQHandler(void) {uint8_t res;if(__MY_TIM_GET_IT_SOURCE(__MY_TIM,TIM_IT_CC1)&&__MY_TIM_GET_FLAG(__MY_TIM, TIM_FLAG_CC1)){__VUART_TX_BIT();if(g_data.tdr_left_bit==0){if(buff_read_byte(&g_data.sbuff,&res)==0){__VUART_SET_DR(res);}else{__MY_TIM_DISABLE_IT(__MY_TIM,TIM_IT_CC1);g_data.in_send=0;}}__MY_TIM_CLEAR_FLAG(__MY_TIM, TIM_FLAG_CC1);}if(__MY_TIM_GET_IT_SOURCE(__MY_TIM,TIM_IT_CC2)&&__MY_TIM_GET_FLAG(__MY_TIM, TIM_FLAG_CC2)){__VUART_RX_BIT();if(g_data.rdr_left_bit==0){buff_save_byte(&g_data.rbuff,g_data.rdr);__MY_TIM_DISABLE_IT(__MY_TIM,TIM_IT_CC2);__MY_EXIT_ENABLE_IT(11);if(g_data.irqfun) g_data.irqfun(res,g_data.context);}__MY_TIM_CLEAR_FLAG(__MY_TIM, TIM_FLAG_CC2);} }void EXTI15_10_IRQHandler(void) {if(__HAL_GPIO_EXTI_GET_FLAG(GPIO_PIN_11)){__VUART_RX_DR();__MY_TIM_CLEAR_FLAG(__MY_TIM, TIM_FLAG_CC2);__MY_TIM_ENABLE_IT(__MY_TIM,TIM_IT_CC2);__MY_EXIT_DISABLE_IT(11);__HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_11);} }

7.接口導(dǎo)出

封裝在vuart.h中定義的串口類,導(dǎo)出接口
用戶只需使用vuart()->init()來初始化,使用vuart()->write()來發(fā)送數(shù)據(jù),使用vuart()->read()來接收數(shù)據(jù)即可。

static vuart_typedef vuart_def= {.init=init,.write=write,.read=read,.set_irqfun=set_irqfun, };vuart_typedef *vuart(void) {return &vuart_def; }

五、驗(yàn)證

在程序線程中編寫類似代碼:

void main(void){vuart()->init();while(1){vuart()->write((uint8_t *)"this msg sent by vuart.\r\n",25);while(vuart()->read((uint8_t *)&vuart_d,1))vuart()->write((uint8_t *)&vuart_d,1);delay(1000);} }

經(jīng)驗(yàn)證可以實(shí)現(xiàn)數(shù)據(jù)發(fā)送和接收,但由于未經(jīng)過大量實(shí)際運(yùn)用,可能還存在一些潛在的問題,不過用整活的眼光來看已經(jīng)成功了。

總結(jié)

以上是生活随笔為你收集整理的STM32关于使用定时器来实现串口通信的整活实验的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。