在用滴答定时器的延时模拟串口通信时序是发现老是接收错误,也不能说是全错,就是断断续续错误,加了校验还是会出错。然后去网上检索以下,发现了采用定时器溢出中断形式来做数据接收,我觉得很不错。把两部分代码整合了一下,放在这里
实测是没有什么大问题,反正是比我原来用延时一位一位的发送强。
main函数代码
#include "stm32f10x.h"
#include "Delay.h"
// 定义串口参数
#define BAUD_RATE 9600
#define START_BIT_DURATION (1.0 / BAUD_RATE)
#define BIT_DURATION (1.0 / BAUD_RATE)
// 定义发送和接收的 GPIO 口
#define TX_PIN GPIO_Pin_9 // PA9
#define RX_PIN GPIO_Pin_10 // PA10
// 定义 BIT_DURATION 的微秒表示
#define BIT_DURATION_US (int)(BIT_DURATION * 1000000)
#define Recive2_Byte 10//接收缓冲器的个数
u8 len2=0;//接收计数
u8 USART2_buf[Recive2_Byte];//接收缓冲区
enum{
COM_START_BIT,
COM_D0_BIT,
COM_D1_BIT,
COM_D2_BIT,
COM_D3_BIT,
COM_D4_BIT,
COM_D5_BIT,
COM_D6_BIT,
COM_D7_BIT,
COM_STOP_BIT,
};
u8 recvStat2= COM_STOP_BIT;
u8 recvData2=0;
uint8_t receivedByte = 0;
void SystemclockInit()
{
// 启动外部时钟
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
// 配置PLL
RCC->CFGR &= ~RCC_CFGR_PLLSRC; // 外部时钟作为PLL输入
RCC->CFGR |= RCC_CFGR_PLLMULL9; // PLL倍频因子,得到72M主频
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1分频,可根据实际情况调整
// 启动PLL
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
// 设置系统时钟为PLL
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
// 调整FLASH等待周期
FLASH->ACR |= FLASH_ACR_LATENCY_2; // 根据主频调整,可能需要其他设置
}
void GPIOxInit() {
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能 GPIOA 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE);
// 配置 TX_PIN 为推挽输出
GPIO_InitStruct.GPIO_Pin = TX_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_SetBits(GPIOA,GPIO_Pin_9);//TXD默认电平拉高
// 配置 RX_PIN 为上拉输入
GPIO_InitStruct.GPIO_Pin = RX_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置外部中断线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource10);
// 配置外部中断
EXTI_InitStructure.EXTI_Line = EXTI_Line10;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 外部中断触发方式为下降沿
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void sendBit(uint8_t bit) {
// 模拟发送位的时序
GPIOA->BSRR = bit ? TX_PIN : (TX_PIN << 16);
// 精确延时
Delay_us(BIT_DURATION_US);
}
void sendByte(uint8_t byte) {
// 发送起始位
sendBit(0);
// 发送数据位
for (int i = 0; i < 8; ++i) {
sendBit((byte >> i) & 1);
}
// 发送停止位
sendBit(1);
}
void USART2_Send(u8* buf,u8 len2)
{
u8 t;
for(t=0;t< len2; t++)
{
sendByte(buf[t]);
}
}
void TIM2_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//时钟使能
//定时器TIM2初始化
TIM_TimeBaseStructure.TIM_Period=arr;//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler=psc;//设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;//设置时钟分割:TDTS=Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;//TIM向上计数模式
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);//根据指定的参数初始化TIMx的时间基数单位
TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能指定的TIM5中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;//TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//先占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//从优先级1级
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//IRQ通道被使能
NVIC_Init(&NVIC_InitStructure);//初始化NVIC寄存器
}
int main(void) {
//初始化时钟源
SystemclockInit();
// 初始化 GPIO
GPIOxInit();
//配置定时器2,溢出时间104us,对应9600 baud
TIM2_Int_Init(104-1,72-1);
// 配置中断优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 这里选择中断分组为2
// 示例:一帧一帧发送数据
uint8_t buff[]={0x01,0x65,0x66,0x67,0x42,0x41};
USART2_Send(buff,sizeof(buff));
while (1) {
}
}
void EXTI15_10_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line10) != RESET) {
// 在这里添加你需要执行的中断处理代码,每bit定时器溢出一次,用停止位和起始位来判断是否启动定时器
//如果是 EXTI_Line10 引发的中断,检查 PA10 引脚是否为低电平,如果是则表示检测到串口数据的起始位(START_BIT):
if((GPIOA->IDR & GPIO_Pin_10 )==0)
{
if(recvStat2==COM_STOP_BIT)//如果当前状态 recvStat2 为停止位 COM_STOP_BIT,则将状态置为开始位 COM_START_BIT,并启动定时器:
{
recvStat2=COM_START_BIT;//将当前的状态设置为开始位
TIM_Cmd(TIM2,ENABLE);//
}
}
EXTI_ClearITPendingBit(EXTI_Line10); // 清除中断标志
}
}
void TIM2_IRQHandler(void)
{
if(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) != RESET)//1bit数据接收完毕,定时器溢出
{
TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
recvStat2++;//RX状态变成数据位
if(recvStat2 == COM_STOP_BIT)//顺延到停止位,表示1帧接收完毕
{
TIM_Cmd(TIM2, DISABLE);//关闭计数器
USART2_buf[len2++] = recvData2;//把数据存入BUFF
if(len2 >Recive2_Byte-1)//数据最多存入Recive2_Byte,多出的覆盖
{
len2=0;
USART2_Send(USART2_buf,Recive2_Byte);//接收完毕的数据发出
}
return;//退出中断,等待下一次的起始位触发EXTI10
}
if((GPIOA->IDR & GPIO_Pin_10 ))//读取RX电平
{
recvData2|=(1<< (recvStat2 - 1));
}else{
recvData2 &= ~(1 << (recvStat2 - 1));
}
}
}
延时函数的代码
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus - 1; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif