豆皮 - STM32开发板入门教程(四) - 串口通讯 UART (原创) 版权所有 STMFANS 原创,转载请保留出处 http://www.stmfans.com/bbs/viewthread.php?tid=1084&extra=page%3D1&frombbs=1 |
一步一步的走 GPIO 按键 LED 定时器都说了 下面开始串口UART咯 本教程的主角是:串口 UART |
通用同步异步收发器(USART)提供了一种灵活的方法来与使用工业标准NR 异步串行数据格式的外部设备之间进行全双工数据交换。 USART利用分数波特率发生器提供宽范围的波特率选择。 它支持同步单向通信和半双工单线通信。它也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。用于多缓冲器配置的DMA方式,可以实现高速数据通信。 主要特性: 全双工的,异步通信 NR 标准格式 分数波特率发生器系统 -发送和接收共用的可编程波特率,最高到4.5Mbits/s 可编程数据字长度(8位或9位) 可配置的停止位 -支持1或2个停止位 LIN主发送同步断开符的能力以及LIN从检测断开符的能力 - 当USART硬件配置成LIN时,生成13位断开符;检测10/11位断开符 发送方为同步传输提供时钟 IRDA SIR 编码器解码器 - 在正常模式下支持3/16位的持续时间 智能卡模拟功能 - 智能卡接口支持ISO7816 -3标准里定义的异步协议智能卡 - 智能卡用到的0.5和1.5个停止位 单线半双工通信 使用DMA的可配置的多缓冲器通信 - 在保留的SRAM里利用集中式DMA缓冲接收/发送字节 单独的发送器和接收器使能位 检测标志 - 接收缓冲器满 - 发送缓冲器空 - 传输结束标志 校验控制 - 发送校验位 - 对接收数据进行校验 四个错误检测标志 - 溢出错误 - 噪音错误 - 帧错误 - 校验错误 10个带标志的中断源 - CTS改变 - LIN断开符检测 - 发送数据寄存器 - 发送完成 - 接收数据寄存器 - 检测到总线为空 - 溢出错误 - 帧错误 - 噪音错误 - 校验错误 多处理器通信 - - 如果地址不匹配,则进入静默模式 从静默模式中唤醒(通过空闲总线检测或地址标志检测) 两种唤醒接收器的方式 - 地址位(MSB) - 空闲总线 |
STM32的串口配置 也挺方便的 首先是配置UART的GPIO口 /******************************************************************************* * Function Name : UART1_GPIO_Configuration * Description : Configures the uart1 GPIO ports. * Input : None * Output : None * Return : None *******************************************************************************/ void UART1_GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; // Configure USART1_Tx as alternate function push-pull GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); // Configure USART1_Rx as input floating GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); } 然后是配置串口参数 /* 如果使用查询的方式发送和接收数据 则不需要使用串口的中断 如果需要使用中断的方式发送和接收数据 则需要使能串口中断 函数原形 void USART_ITConfig(USART_TypeDef* USARTx, u16 USART_IT, FunctionalState NewState) 功能描述 使能或者失能指定的 USART 中断 USART_IT 描述 USART_IT_PE 奇偶错误中断 USART_IT_TXE 发送中断 USART_IT_TC 传输完成中断 USART_IT_RXNE 接收中断 USART_IT_IDLE 空闲总线中断 USART_IT_LBD LIN中断检测中断 USART_IT_CTS CTS中断 USART_IT_ERR 错误中断 */ /******************************************************************************* * Function Name : UART1_Configuration * Description : Configures the uart1 * Input : None * Output : None * Return : None *******************************************************************************/ void UART1_Configuration(void) { USART_InitTypeDef USART_InitStructure; /* USART1 configured as follow: - BaudRate = 9600 baud - Word Length = 8 Bits - One Stop Bit - No parity - Hardware flow control disabled (RTS and CTS signals) - Receive and transmit enabled */ USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No ; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; /* Configure the USART1*/ USART_Init(USART1, &USART_InitStructure); /* Enable USART1 Receive and Transmit interrupts */ USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); /* Enable the USART1 */ USART_Cmd(USART1, ENABLE); } |
发送一个字符 [/******************************************************************************* * Function Name : Uart1_PutChar * Description : printf a char to the uart. * Input : None * Output : None * Return : None *******************************************************************************/ u8 Uart1_PutChar(u8 ch) { /* Write a character to the USART */ USART_SendData(USART1, (u8) ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) { } return ch; } |
发送一个字符串 /******************************************************************************* * Function Name : Uart1_PutString * Description : print a string to the uart1 * Input : buf为发送数据的地址 , len为发送字符的个数 * Output : None * Return : None *******************************************************************************/ void Uart1_PutString(u8* buf , u8 len) { for(u8 i="0";i<len;i++) { Uart1_PutChar(*buf++); } } |
如果UART使用中断发送数据 则需要修改stm32f10x_it.c 中的串口中断函数 并且需要修改void NVIC_Configuration(void)函数 在中断里面的处理 原则上是需要简短和高效 下面的流程是 如果接收到255个字符或者接收到回车符 则关闭中断 并且把标志位UartHaveData 置1 /******************************************************************************* * Function Name : USART1_IRQHandler * Description : This function handles USART1 global interrupt request. * Input : None * Output : None * Return : None *******************************************************************************/ void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { /* Read one byte from the receive data register */ RxBuffer[ RxCounter ] = USART_ReceiveData(USART1); if( RxCounter == 0xfe || '\r' == RxBuffer[ RxCounter ] ) { /* Disable the USART1 Receive interrupt */ USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); RxBuffer[ RxCounter ] = '\0'; UartHaveData = 1; } RxCounter++; } } 修改NVIC_Configuration函数 /******************************************************************************* * Function Name : NVIC_Configuration * Description : Configures NVIC and Vector Table base location. * Input : None * Output : None * Return : None *******************************************************************************/ void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; #ifdef VECT_TAB_RAM /* Set the Vector Table base location at 0x20000000 */ NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); #else /* VECT_TAB_FLASH */ /* Set the Vector Table base location at 0x08000000 */ NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); #endif /* Configure the NVIC Preemption Priority Bits */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); /* Enable the USART1 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } |
至此 串口就可以工作起来了 附件中的程序功能是 开机后 从串口中输出2行信息 然后就等待接收串口数据 并且把接收到的数据发回到PC机上来 附件有2个 一个是查询方式的 一个是中断方式的 |
豆皮 - STM32开发板基础教程(七) - ADC with DMA(原创) 版权所有 STMFANS 原创,转载请保留出处
|
下面来讲一下STM32的ADC应用。 先闲扯一点其他事情,是我自己的理解。 STM32的优点在哪里? 除去宣传环节,细细分析。 STM32时钟不算快,72MHZ, 也不能扩展大容量的RAM FLASH, 同样没有DSP那样强大的指令集。 它的优势在哪里呢? ---就在快速采集数据,快速处理上。 ARM的特点就是方便。 这个快速采集,高性能的ADC就是一个很好的体现, 12位精度,最快1uS的转换速度,通常具备2个以上独立的ADC控制器, 这意味着, STM32可以同时对多个模拟量进行快速采集, 这个特性不是一般的MCU具有的。 以上高性能的ADC,配合相对比较块的指令集和一些特色的算法支持, 就构成了STM32在电机控制上的强大特性。 好了,正题,怎末做一个简单的ADC,注意是简单的, ADC是个复杂的问题,涉及硬件设计,电源质量,参考电压,信号预处理等等问题。 我们只就如何在MCU内完成一次ADC作讨论。 谈到ADC,我们还要第一次引入另外一个重要的设备DMA. DMA是什么东西呢。 通常在8位单片机时代,很少有这个概念。 在外置资源越来越多以后, 我们把一个MCU内部分为 主处理器 和 外设两个部分。 主处理器当然是执行我们指令的主要部分, 外设则是 串口 I2C ADC 等等用来实现特定功能的设备 回忆一下,8位时代,我们的主处理器最常干的事情是什么? 逻辑判断?不是。那才几个指令 计算算法?不是。大部分时候算法都很简单。 事实上,主处理器就是作个搬运工, 把USART的数据接收下来,存起来 把ADC的数据接收下来,存起来 把要发送的数据,存起来,一个个的往USART里放。 ………… 为了解决这个矛盾, 人们想到一个办法,让外设和内存间建立一个通道, 在主处理器允许下, 让外设和内存直接 读写,这样就释放了主处理器, 这个东西就是DMA。 打个比方: 一个MCU是个公司。 老板就是主处理器 员工是外设 仓库就是内存 从前 仓库的东西都是老板管的。 员工需要原料工作,就一个个报给老板,老板去仓库里一个一个拿。 员工作好的东西,一个个给老板,老板一个个放进仓库里。 老板很累,虽然老板是超人,也受不了越来越多的员工和单子。 最后老板雇了一个仓库保管员,它就是DMA 他专门负责 入库和出库, 只需要把出库 和入库计划给老板过目 老板说OK,就不管了。 后面的入库和出库过程, 员工只需要和这个仓库保管员打交道就可以了。 --------闲话,马七时常想,让设备与设备之间开DMA,岂不更牛X 比喻完成。 ADC是个高速设备,前面提到。 而且ADC采集到的数据是不能直接用的。即使你再小心的设计外围电路,测的离谱的数据总会出现。 那么通常来说,是采集一批数据,然后进行处理,这个过程就是软件滤波。 DMA用到这里就很合适。让ADC高速采集,把数据填充到RAM中,填充一定数量,比如32个,64个 MCU再来使用。 -----多一句,也可以说,单次ADC毫无意义。 |
下面我们来具体介绍,如何使用DMA来进行ADC操作。 初始化函数包括两部分,DMA初始化和 ADC初始化 我们有多个管理员--DMA 一个管理员当然不止管一个DMA操作。所以DMA有多个Channel //ADC with DMA Init #define ADC_Channel ADC_Channel0 #define ADC1_DR_Address ((u32)0x4001244C) void ADCWithDMAInit() { //DMA init; Using DMA channel 1 DMA_DeInit(DMA1_Channel1); //开启DMA1的第一通道 DMA_InitStruct.DMA_PeripheralBaseAddr = ADC1_DR_Address; //DMA对应的外设基地址,这个地址走Datasheet查 DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //转换结果的数据大小 DMA_InitStruct.DMA_MemoryBaseAddr = (unsigned long)&ADC_ConvertedValue; // DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //DMA的转换模式是SRC模式,就是从外设向内存中搬运, DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //M2M模式禁止,memory to memory,这里暂时用不上,以后介 绍 DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //DMA搬运的数据尺寸,注意ADC是12位的, HalfWord就是16位 DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Disable; //接收一次数据后,目标内存地址是否后移--重 要概念,用来采集多个数据的 DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //接收一次数据后,设备地址是否后移 DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; //转换模式,循环缓存模式,常用,M2M果果开启了,这个模式失效 。 DMA_InitStruct.DMA_Priority = DMA_Priority_High; //DMA优先级,高 DMA_InitStruct.DMA_BufferSize = 1; //DMA缓存大小,1个 DMA_Init(DMA1_Channel1,&DMA_InitStruct); // Enable DMA1 DMA_Cmd(DMA1_Channel1, ENABLE); } void ADCx_Init(unsigned char ADC_Channel) { ADC_DeInit(ADC1); //开启ADC1 ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; //转换模式,为独立转换。转换模式太多了,以后深究 ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; //对齐方式,ADC结果是12位的,显然有个对齐左边还是右边 的问题。一般是右对齐 ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; //连续转换模式开启 ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //ADC外部出发开关,关闭 ADC_InitStruct.ADC_NbrOfChannel = 2; //开启通道数,2个 ADC_InitStruct.ADC_ScanConvMode = ENABLE; //扫描转换模式开启 ADC_Init(ADC1, &ADC_InitStruct); ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_239Cycles5); //规则组通道设置,关键函数 转 换器ADC1,选择哪个通道channel,规则采样顺序,1到16,以后解释详细含义,最后一个参数是转换时间,越长越准越稳定 // ADC1 to DMA, Enable ADC_DMACmd(ADC1, ENABLE); //ADC命令,和DMA关联。 //ADC1 Enable ADC_Cmd(ADC1,ENABLE); //开启ADC1 //Reset the Calibration of ADC1 ADC_ResetCalibration(ADC1); //重置校准 //wait until the Calibration's finish while(ADC_GetResetCalibrationStatus(ADC1)) //等待重置校准完成 ; ADC_StartCalibration(ADC1); //开始校准 while(ADC_GetCalibrationStatus(ADC1)) //等待校准完成 ; ADC_SoftwareStartConvCmd(ADC1, ENABLE); //连续转换开始,从选择开始,MCU可以不用管了,ADC将通过DMA不断刷新 制定RAM区 // Attach them; } 最后讲讲滤波算法 滤波的方法以后会开个专题。 特别提一下---没有完美的滤波算法,只有合适的滤波算法。 需要综合考虑信号特点,噪声特点,控制对象等等, 这里用个最简单的滤波算法,均值滤波。 采样16次,取平均值,吼吼,在豆皮上跳动还是蛮小的,合适,吼吼 //16ms finish a ADC detection // return mv unsigned int ADC_filter(void) { unsigned int result="0"; unsigned char i; for(i=16;i>0;i--) { Delay_xms(1); result += ADC_ConvertedValue; } return (unsigned int)(((unsigned long)(result>>4))*3300>>12); } 完整工程文件,IAR520 JLink 豆皮, |
豆皮 - STM32开发板入门教程(十八) - 工业现场总线 CAN (原创) 版权所有 STMFANS 原创,转载请保留出处 http://www.stmfans.com/bbs/viewthread.php?tid=3046&extra=page%3D1 |
1、首先通读手册中关于CAN的文档,必须精读。 STM32F10xxx 参考手册Rev7V3.pdf 需要精读的部分为 RCC 和 CAN 两个章节。 为什么需要精读 RCC 呢?因为我们将学习 CAN 的波特率的设置,将要使用到 RCC 部分的设置,因此推荐大家先复习下这部分中的几个时钟。 关于STM32的can总线简单介绍 bxCAN是基本扩展CAN(Basic Extended CAN)的缩写,它支持CAN协议2.0A和2.0B。它的设计目标是,以最小的CPU负荷来高效处理大量收到的报文。它也支持报文发送的优先级要求(优先级特性可软件配置)。 对于安全紧要的应用,bxCAN提供所有支持时间触发通信模式所需的硬件功能。 主要特点 ·支持CAN协议2.0A和2.0B主动模式 · 波特率最高可达1兆位/秒 ·支持时间触发通信功能 发送 ·3个发送邮箱 · 发送报文的优先级特性可软件配置 ·记录发送SOF时刻的时间戳 接收 · 3级深度的2个接收FIFO ·14个位宽可变的过滤器组-由整个CAN共享 · 标识符列表 ·FIFO溢出处理方式可配置 ·记录接收SOF时刻的时间戳 可支持时间触发通信模式 ·禁止自动重传模式 ·16位自由运行定时器 ·定时器分辨率可配置 ·可在最后2个数据字节发送时间戳 管理 ·中断可屏蔽 · 邮箱占用单独1块地址空间,便于提高软件效率 |
2、STM32FVBT6 的 can 的工作模式分为 /* CAN operating mode */ #define CAN_Mode_Normal ((u8)0x00) /* normal mode */ #define CAN_Mode_LoopBack ((u8)0x01) /* loopback mode */ #define CAN_Mode_Silent ((u8)0x02) /* silent mode */ #define CAN_Mode_Silent_LoopBack ((u8)0x03) /* loopback combined with silent mode */ 在此章我们的豆皮教程中我们将使用到 CAN_Mode_LoopBack 和 CAN_Mode_Normal 两种模式。 我们第一步做的就是使用运行在 CAN_Mode_LoopBack 下进行自测试。 在参考手册中 CAN_Mode_LoopBack (环回模式) 的定义如下: 环回模式可用于自测试。为了避免外部的影响,在环回模式下CAN内核忽略确认错误(在数据/远程帧的确认位时刻,不检测是否有显性位)。在环回模式下,bxCAN在内部把Tx输出回馈到Rx输入上,而完全忽略CANRX引脚的实际状态。发送的报文可以在CANTX引脚上检测到。 因此比较适合我们只有一块豆皮的情况下面测试 STM32 的 CAN 部分 BSP 程序。 |
3、STM32FVBT6 中的 can 物理引脚脚位可以设置成三种:默认模式,重定义地址1模式,重定义地址2模式。 在我们的豆皮中我们使用的是重定义地址2模式,即CANRX,CANTX 分别重定义到 PD0,PD1 引脚上面。 因此我们软件中第一步要进行重定义的操作: ------------------------------------------------------------------------ /* Configure CAN pin: RX */ //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //GPIO_Init(GPIOB, &GPIO_InitStructure); /* Configure CAN pin: TX */ //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //GPIO_Init(GPIOB, &GPIO_InitStructure); /* Configure CAN Remap 重影射 */ //GPIO_PinRemapConfig(GPIO_Remap1_CAN, ENABLE); ------------------------------------------------------------------------- /* Configure CAN pin: RX */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOD, &GPIO_InitStructure); /* Configure CAN pin: TX */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOD, &GPIO_InitStructure); /* Configure CAN Remap 重影射 */ GPIO_PinRemapConfig(GPIO_Remap2_CAN, ENABLE); ------------------------------------------------------------------------- /* Configure CAN pin: RX */ //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure CAN pin: TX */ //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //GPIO_Init(GPIOA, &GPIO_InitStructure); ------------------------------------------------------------------------- 设置完 CAN 的引脚之后还需要打开 CAN 的时钟: /* CAN Periph clock enable */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN, ENABLE); |
4、我们需要搞明白CAN波特率的设置,这个章节也是使用CAN的最重要的部分之一,因为这实际应用中我们需要根据我们实际的场合来选择 CAN 的波特率。 一般情况下面1M bps 的速率下可以最高可靠传输 40 米以内的距离。 在 50K 以下的波特率中一般可以可靠传输数公里远。 对于波特率的设置需要详细学习参考手册对应部分的解释。我们在调试软件的时候可以使用示波器来测试 CANTX 引脚上的波形的波特率,这样可以得到事半功倍的效果,大大的缩短调试学习的时间。 // *************************************************************** // BaudRate = 1/ NominalBitTime // NominalBitTime = 1tq+tBS1+tBS2 // tq = (BRP[9:0] + 1) x tPCLK // tPCLK = CAN's clock = APB1's clock // **************************************************************** 也就是BaudRate = APB1/((BS1 + BS2 + 1)*Prescaler) 这里注意的是采用点的位置,也就时BS1,BS2的设置问题,这里我也找了一些资料,抄录下来给大家,是CANopen协议中推荐的设置。 1Mbps速率下,采用点的位置在6tq位置处,BS1=5,BS2=2 500kbps速率下,采用点的位置在8tq位置处,BS1=7,BS2=3 250kbps速率下,采用点的位置在14tq位置处,BS1=13,BS2=2 125k,100k,50k,20k,10k的采用点位置与250K相同。 因此我们需要重视的有软件中的这么几个部分: //设置 AHB 时钟(HCLK) //RCC_SYSCLK_Div1 AHB 时钟 = 系统时钟 RCC_HCLKConfig(RCC_SYSCLK_Div8); //设置低速 AHB 时钟(PCLK1) //RCC_HCLK_Div2 APB1 时钟 = HCLK / 2 RCC_PCLK1Config(RCC_HCLK_Div2); // PLLCLK = 8MHz * 8 = 64 MHz //设置 PLL 时钟源及倍频系数 RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_8); CAN 波特率设置中需要的就是PCLK1 的时钟。 CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack; CAN_InitStructure.CAN_SJW=CAN_SJW_1tq; CAN_InitStructure.CAN_BS1=CAN_BS1_8tq; CAN_InitStructure.CAN_BS2=CAN_BS2_7tq; CAN_InitStructure.CAN_Prescaler=5; 通过上面部分的时钟设置我们已经可以算出我们的波特率了 CAN_bps = PCLK1 / ((1 + 7 + 8) * 5) = 25K bps 大家也可以实际测试中修改时钟值来通过示波器测试我们需要的波特率是否正确例如将PLLCLK 设置降低一半: // PLLCLK = 8MHz * 4 = 32 MHz //设置 PLL 时钟源及倍频系数 RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_4); 那么我们得到的CAN_bps也会降低一半。 接下来还可以修改 HCLK 和 PCLK1 ,其实最终这几个分频和倍频值最终影响的都是 PCLK1。 通过几次试验,相信大家应该很容易掌握波特率的设置了。 设置完波特率我们直接测试函数:TestStatus CAN_Polling(void) // CAN transmit at 25Kb/s and receive by polling in loopback mode TestRx = CAN_Polling(); if (TestRx == FAILED) { // Turn on led connected to PC.08 pin (LED4) // For DP-STM32F use LED4 connected to PC.12 GPIO_ResetBits(GPIOC, GPIO_Pin_12); } else { // Turn on led connected to PC.06 pin (LED2) // For DP-STM32F use LED2 connected to PC.11 GPIO_ResetBits(GPIOC, GPIO_Pin_11); } 大家可以仿真程序,当程序中 Test 等于 Passed 那么说明 Loopback 模式测试通过了。 到此时说明如果大家只有一块CAN模块的时候学习可以告一个段落了,不过这个并不代表大家就已经掌握了 CAN 了,正真要掌握它,大家还是需要看大量的 CAN 部分的资料,参考手册部分的也是不够的,市面上有几本专门介绍现场总线和CAN总线的书,推荐大家买来经常翻翻看看,这样到需要实际应用的时候才可以做到如鱼得水。 |
5、完成了单板的 loopback 模式的测试之后接下来我们需要学习的就是多机通讯了,当然如果你只有一块豆皮开发板当然你就不能做这部分的试验了,只能先看看这部分的程序和教程了。 在这里我们需要准备两块豆皮板,使用三根线将 CANH,CANL,GND 三根线直连,当然需要把跳线 F 处的跳至终端电阻处,当两块板子都跳好后我们使用万用表测量下 CANH和CANL之间的电阻是否为 60 欧姆(豆皮上大约为 62欧姆)。 正常模式 在初始化完成后,软件应该让硬件进入正常模式,以便正常接收和发送报文。软件可以通过对CAN_MCR寄存器的INRQ位清’0’,来请求从初始化模式进入正常模式,然后要等待硬件对CAN_MSR寄存器的INAK位置’1’的确认。在跟CAN总线取得同步,即在CANRX引脚上监测到11个连续的隐性位(等效于总线空闲)后,bxCAN才能正常接收和发送报文。 不需要在初始化模式下进行过滤器初值的设置,但必须在它处在非激活状态下完成(相应的FACT位为0)。而过滤器的位宽和模式的设置,则必须在初始化模式中进入正常模式前完成。 准备工作做完我们需要设置软件,让一块豆皮板发送一块接收。 / CAN transmit at 100Kb/s and receive by interrupt in normal mode TestRx = CAN_Interrupt(); if (TestRx == FAILED) { // Turn on led connected to PC.09 pin (LED3) // For DP-STM32F use LED3 connected to PC.10 GPIO_ResetBits(GPIOC, GPIO_Pin_10); } else { // Turn on led connected to PC.07 pin (LED8) // For DP-STM32F use LED8 connected to PD.05 GPIO_ResetBits(GPIOD, GPIO_Pin_5); } |
豆皮 - STM32开发板入门教程(三) - SysTick 定时器 (原创) 版权所有 STMFANS 原创,转载请保留出处 http://www.stmfans.com/bbs/viewthread.php?tid=1069&page=1&extra=page%3D1 |
OK 下面继续做偶们的SysTick定时器咯 呵呵 本教程的主角是:SysTick |
通常实现Delay(N)函数的方法为: for(i = 0; i <= x; i ++); x --- 对应于 对应于N 毫秒的循环值 |
对于STM32系列微处理器来说,执行一条指令只有几十个ns,进行for循环时,要实现N毫秒的x值非常大,而且由于系统频率的宽广,很难计算出延时N毫 秒的精确值。针对STM32微处理器,需要重新设计一个新的方法去实现该功能,以实现在程序中使用Delay(N)。 |
Cortex-M3的内核中包含一个SysTick时钟。SysTick 为一个24位递减计数器,SysTick设定初值并使能后,每经过1个系统时钟周期,计数值就减1。计数到0时,SysTick计数器自动重装初值并继续计数,同时内部的COUNTFLAG标志会置位,触发中断(如果中断使能)。 在STM32的应用中,使用Cortex-M3 内核的SysTick作为定时时钟,设定每一毫秒产生一次中断,在中断处理函数 里对N减一,在Delay(N)函数中循环检测N是否为0,不为0则进行循环等待;若为0则关闭SysTick时钟,退出函数。 |
注: 全局变量TimingDelay 必须定义为volatile 延迟时间将不随系统时钟频率改变。 |
外部晶振为8MHz,9倍频,系统时钟为72MHz,SysTick的最高频率为9MHz(最大为HCLK/8),在这个条件下,把SysTick 效验值设置成9000,将SysTick 时钟设置为9MHz, 就能够产生1ms的时间基值,即SysTick产生1ms的中断。 |
使用ST的函数库使用systick的方法 1、调用SysTick_CounterCmd() 失能SysTick计数器 2、调用SysTick_ITConfig () 失能SysTick中断 3、调用SysTick_CLKSourceConfig() 设置SysTick时钟源。 4、调用SysTick_SetReload() 设置SysTick重装载值。 5、调用SysTick_ITConfig () 使能SysTick中断 6、调用SysTick_CounterCmd() 开启SysTick计数器 |
SysTick 配置函数 /******************************************************************************* * Function Name : SysTick_Config * Description : Configures SysTick * Input : None * Output : None * Return : None *******************************************************************************/ //SysTick设置 void SysTick_Config(void) { /* Disable SysTick Counter */ SysTick_CounterCmd(SysTick_Counter_Disable); /* Disable the SysTick Interrupt */ SysTick_ITConfig(DISABLE); /* Configure HCLK clock as SysTick clock source */ SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); /* SysTick interrupt each 1000 Hz with HCLK equal to 72MHz */ SysTick_SetReload(9000); /* Enable the SysTick Interrupt */ SysTick_ITConfig(ENABLE); } |
Delay_Ms 延迟一毫秒函数 /******************************************************************************* * Function Name : Delay_Ms * Description : Inserts a delay time. * Input : nTime: specifies the delay time length, in milliseconds. * Output : None * Return : None *******************************************************************************/ void Delay_Ms(u32 nTime) { /* Enable the SysTick Counter */ SysTick_CounterCmd(SysTick_Counter_Enable); TimingDelay = nTime; while(TimingDelay != 0); /* Disable SysTick Counter */ SysTick_CounterCmd(SysTick_Counter_Disable); /* Clear SysTick Counter */ SysTick_CounterCmd(SysTick_Counter_Clear); } |
TimingDelayMs_Decrement 中断调用函数 /******************************************************************************* * Function Name : TimingDelayMs_Decrement * Description : Decrements the TimingDelay variable. * Input : None * Output : TimingDelay * Return : None *******************************************************************************/ void TimingDelay_Decrement(void) { if (TimingDelay != 0x00) { TimingDelay--; } } |
SysTickHandler 中断进入函数 /******************************************************************************* * Function Name : SysTickHandler * Description : This function handles SysTick Handler. * Input : None * Output : None * Return : None *******************************************************************************/ void SysTickHandler(void) { TimingDelay_Decrement(); } |
NVIC_Configuration 中断向量表配置 /******************************************************************************* * Function Name : NVIC_Configuration * Description : Configures NVIC and Vector Table base location. * Input : None * Output : None * Return : None *******************************************************************************/ void NVIC_Configuration(void) { #ifdef VECT_TAB_RAM /* Set the Vector Table base location at 0x20000000 */ NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0); #else /* VECT_TAB_FLASH */ /* Set the Vector Table base location at 0x08000000 */ NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); #endif } |
完整工程在附件里 敬请继续关注 豆皮的的教程会逐步推出 |
豆皮 - STM32开发板入门教程(二) - 按键 + 蜂鸣器 (原创) 版权所有 STMFANS 原创,转载请保留出处 http://www.stmfans.com/bbs/viewthread.php?tid=1064&extra=page%3D1 |
呵呵 在教程一的基础上 继续做偶们的按键和蜂鸣器 呵呵 豆皮板子上一共有5个按键(4个功能按键 + 一个RESET按键) 那么 选择 LED5 LED6 LED7 LED8 与按键 KEY1 KEY2 KEY3 KEY4 一一对应 按键按下的时候 对应的LED灯就亮起来 然后只要有按键安下 蜂鸣器就响 呵呵 |
初始化 |
//配置KEY使用的GPIO口 KEY_GPIO_Configuration(); 和 //配置buzzer蜂鸣器使用的端口 BUZZER_GPIO_Configuration(); |
分别对应的函数体 |
按键的初始化函数体 /******************************************************************************* * Function Name : KEY_GPIO_Configuration * Description : Configures the KEY GPIO ports. * Input : None * Output : None * Return : None *******************************************************************************/ void KEY_GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; //按键使用的GPIOC 的 Pin6 Pin7 Pin8 端口 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 ; // GPIO口的速度 作为按键 10MHz 对于一般的用途 足以 呵呵 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //按键端口设置为 浮空输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOC, &GPIO_InitStructure); //按键使用的GPIOB 的 Pin15 端口 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ; // GPIO口的速度 作为按键 10MHz 对于一般的用途 足以 呵呵 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //按键端口设置为 浮空输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStructure); } |
蜂鸣器的初始化函数体 /******************************************************************************* * Function Name : BUZZER_GPIO_Configuration * Description : Configures the BUZZER GPIO ports. * Input : None * Output : None * Return : None *******************************************************************************/ void BUZZER_GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; //蜂鸣器使用的GPIOB 的 Pin9 端口 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ; //蜂鸣器的IO口速度暂时也设置为10MHz 如果以后想用PWM控制蜂鸣器发出各种不同声音 就需要把速度设置高一点 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; //蜂鸣器端口设置为 推挽输出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); } |
蜂鸣器的接法 也是标准接法 呵呵 如下图所示 |
程序的流程是: 1. 依次扫描4个按键 使用一个u8变量的低4位存储扫描结果 如果对应位上的按键被按下 则该位置1 否则 置0 可以支持多个按键同时按下 2. 处理扫描结果 依次判断u8变量的低4位 如果某个按键被按下 则点亮对应的LED灯 3. 判断当前是否有按键按下 如果有 则蜂鸣器响 否则 蜂鸣器不响 |
按键扫描函数 /******************************************************************************* * Function Name : KEY_GPIO_Scanning * Description : 依次扫描4个按键 使用一个u8变量的低4位存储扫描结果 * Input : None * Output : None * Return : 扫描的结果 有效数据是低4位 *******************************************************************************/ u8 KEY_GPIO_Scanning(void) { u8 scan_bit; //单个按键扫描变量 u8 scan_sum; //四个按键总的情况变量 scan_sum低四位的每一位对应一个按键 scan_bit = 0; scan_sum = 0; //扫描按键 scan_bit = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_8); //如果按键按下 则延迟 再扫描 然后根据判断处理 if( 0x01 == scan_bit ) { delay(); scan_bit = 0; scan_bit = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_8); if( 0x01 == scan_bit ) scan_sum |= GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_8); scan_bit = 0; } scan_bit = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7); if( 0x01 == scan_bit ) { delay(); scan_bit = 0; scan_bit = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7); if( 0x01 == scan_bit ) scan_sum |= GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_7)<<1; scan_bit = 0; } scan_bit = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6); if( 0x01 == scan_bit ) { delay(); scan_bit = 0; scan_bit = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6); if( 0x01 == scan_bit ) scan_sum |= GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6)<<2; scan_bit = 0; } scan_bit = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15); if( 0x01 == scan_bit ) { delay(); scan_bit = 0; scan_bit = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15); if( 0x01 == scan_bit ) scan_sum |= GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15)<<3; scan_bit = 0; } return scan_sum; } |
按键处理函数 /******************************************************************************* * Function Name : LED_For_Key_Shine * Description : 按键处理函数 * Input : u8 变量 按键扫描结果 * Output : None * Return : None *******************************************************************************/ void LED_For_Key_Shine(u8 scan_sum) { //使用一个8位变量 表示当前是否有按键按下 // 初始值为4 如果没有按键按下 扫描完scan_sum之后 该值为0 如果该值不为0 则证明有按键按下 u8 key_count = 4 ; if( scan_sum & 0x01 ) { GPIO_SetBits(GPIOD, GPIO_Pin_2); key_count--; } else { GPIO_ResetBits(GPIOD, GPIO_Pin_2); key_count++; } if( scan_sum & 0x02 ) { GPIO_SetBits(GPIOD, GPIO_Pin_3); key_count--; } else { GPIO_ResetBits(GPIOD, GPIO_Pin_3); key_count++; } if( scan_sum & 0x04 ) { GPIO_SetBits(GPIOD, GPIO_Pin_4); key_count--; } else { GPIO_ResetBits(GPIOD, GPIO_Pin_4); key_count++; } if( scan_sum & 0x08 ) { GPIO_SetBits(GPIOD, GPIO_Pin_5); key_count--; } else { GPIO_ResetBits(GPIOD, GPIO_Pin_5); key_count++; } //如果 key_count为0 则表示当前没有按键被按下 蜂鸣器不响 否则 蜂鸣器响 if( key_count == 0 ) GPIO_ResetBits(GPIOB, GPIO_Pin_9); else GPIO_SetBits(GPIOB, GPIO_Pin_9); } |
豆皮 - STM32开发板入门教程(六) - I2C--24Cxx (原创) 版权所有 STMFANS 原创,转载请保留出处
|
从这一讲起,我们将陆续介绍各种通讯协议。 芯片与芯片之间,系统与系统之间,是需要通讯的。 最直观的方法是,I/O连I/O,一个输出,一个输入 当然这也是一种很主要的通讯方式。 但是,I/O操作是需要浪费系统时钟的, 另外,大家以后会慢慢体会,I/O是很珍贵的,很多时候,省下一个I/O可以减少一笔开支。 所以就有了通讯协议的出现,根据规定的通讯规则, 芯片互相通讯,并设置相应的控制器,以分担主线程的负担。 这里提一下 全双工 和 半双工 的概念。 收发可同时进行,就是全双工的。 主要的通讯方式包括: 1 )USART (UART,SCI),最古老的通讯方式,稳定可靠。UART是早期版本,半双工;USART是最新版本,支持地址码,可以收发同时,全双工工作;SCI则是简化版本。USART加上流量控制,奇偶校验后,稳定性很高。随着DMA的引入,它的通讯速度可以提到很高了,STM32可以到2万多的波特率,但是缺点是耗费的IO相对比较多。 2 )SPI 大数据量通讯的典范,基本不支持地址特性,如果想要地址特性,需要想其他办法,比如片选。它是全双工。在存储芯片,显示芯片上常见。最典型的是SD卡。但占用IO也比较多,最少3个。 3 )I2C 为芯片与芯片之间的短距离通讯设计,速度一般不能太快,但有个最大的优点,就是占用IO少,只要2个,一个SCL时钟,一个SDA数据即可。而且有优良的地址特性。非双工。数据量不大,又要多个芯片互通,最佳选择。特别指出,近来很热的SMbus,就是I2C的扩展。---附注:听说ZLG有个芯片可以把I2C的通讯变成差分的,从而让距离超远。好像RS232到RS422的改变。无语中…… 从上面的分析可以看出 三种协议各有优点, 具体选择要看 流量、地址是否需要、是否需要双工。 再者要考虑芯片的内置资源。 比如很多芯片没有USART,只有SCI,留神。 特别指出,AVR的TWI就是I2C,只是为了逃避给飞利浦专利费。 |
现在解释I2C怎末做。 STM32内置的I2C十分强大。除了I2C基本功能,还支持SMBus。 这一讲我们从最简单的I2C设备,EEPROM芯片 24Cxx说起。 I2C遵循一种主机-从机模式。 类似USB(扯多了,USB我自己都搞不太明白) 即:通讯,无论发送或者接收,必须由主机发起。 主机好比老板,从机好比员工。 发送数据: 老板喊:“9527”。9527员工响应,然后老板发送数据给9527 接收数据: 老板喊:“9527给我数据”。9527员工响应。然后9527送数据给老板。 理解了这个过程。I2C的代码就好理解了。 我们所用来示范的24Cxx系列是最常用的EEPROM芯片。 前面提到了一个地址码, 24Cxx的地址码是固定的, 8位如下: 1 0 1 0 A2 A1 A0 0 A2 A1 A0分别是它三个管脚的电平 24Cxx 理解起来有一个特别之处。 24Cxx 包括 01/02/04/08/16 四种,容量关系刚好和数字一样。1K 2K 4K 8K 16K 24C02 最为常见, 它的三个地址管脚A2 A1 A0都是可用的, A2 A1 A0 有8中电平组合,也就是说,可以有8个 24C02 挂载同一个I2C总线上。 24C04呢, A0管脚就失效了,只有A2 和 A1 有用,四种组合,最多有4个24C04在总线上, 以此类推。24C16只能有一个在总线上。 这里就不好理解了,为什么要这样呢。 事实是一片 24C16 == 8片24C02 总线挂到一起。A2 A1 A0虽然起不到设置作用了,但你使用地址码还是会访问到特定的区域。 明白了吧。所以其实24C系列的代码是通用的。 地址码也是固定的。就是 0xA0 0xA2 0xA4 0xA6 0xA8 0xAA 0xAC 0xAE 好,我们以24C16为范例吧。 |
IO设置在I2C1上,无Remap,复用开漏输出。I2C 总线是挂4.7k电阻上拉到高电平的。 //-----------------------I2C-------------------------------------------- /* Configure I2C1 pins: SCL and SDA */ GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; //复用开漏输出 GPIO_Init(GPIOB, &GPIO_InitStruct); I2C init函数: void i2c_24c_init(I2C_TypeDef *I2Cx) { I2C_InitTypeDef I2C_InitStruct; I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // I2C模式 I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // ACK 在通讯中常见,握手包,即发送到了一个数据,接收方回一句,我收到鸟。 I2C_InitStruct.I2C_ClockSpeed = I2C_Speed; // I2C 速度设置,一般是40KHZ,400KHZ是极限,一般到不了那么高 I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; //快速模式下的选项,这里先不讲,100KHZ以上才有用 I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //应答地址码长度,7位或者10位,24C是7位 // EEprom Block Select; I2C_InitStruct.I2C_OwnAddress1 = I2C_Slave_Adress7; //第一个设备自身地址 I2C_Cmd(I2Cx,ENABLE); //开启I2C I2C_Init(I2Cx,&I2C_InitStruct); //将刚刚的设置送进去 } 注意:现在的片子一般都不止一个I2C。所以用了上述模式,请详细看注释。 写一个字节进EEPROM: 参数解释:Byte待写的字节,WriteAddr预计写入的地址,ByteToWrite写多少给字节,EE24cBlockSelect选择EEPROM相应的区域(I2C地址),*I2Cx,I2C设备指针 void i2c_24c_byte_write(unsigned char Byte, unsigned char WriteAddr, unsigned int ByteToWrite, unsigned char EE24cBlockSelect,I2C_TypeDef *I2Cx) { // Start the I2C I2C_GenerateSTART(I2Cx,ENABLE); //打开I2C,开始发送过程 //not recommanded, stupid way while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); //设置主机模式 I2C_Send7bitAddress(I2Cx,EE24cBlockSelect,I2C_Direction_Transmitter); //发送片选,选择哪一片区域写。i2C地址区分 // when get ACK, means Set Success while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //等待这次选择过程完成 I2C_SendData(I2Cx, WriteAddr); //发送要写入的地址码 while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待字节发送完成 I2C_SendData(I2Cx, Byte); //发送要写的字节 while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待直到字节发送完成 I2C_GenerateSTOP(I2Cx, ENABLE); //发送过程结束。 } 读EEPROM函数类似,却稍微复杂。 参数说明:pBuffer接收I2C数据的缓冲区,Addr读的地址,NumToRead读多少个字节,ee24cblockselect读哪个区域,I2Cx i2c设备指针 void i2c_24c_buffer_read(unsigned char *pBuffer, unsigned char Addr,unsigned char NumToRead,unsigned char EE24cBlockSelect, I2C_TypeDef *I2Cx) { //open I2C I2C_GenerateSTART(I2Cx, ENABLE); //开始发送 while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))); //设置自己为主机 I2C_Send7bitAddress(I2Cx,EE24cBlockSelect,I2C_Direction_Transmitter); //设置自己为发送 while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))); //等待主机发送模式设置成功 I2C_Cmd(I2Cx,ENABLE); //使能I2C I2C_SendData(I2Cx, Addr); //发送地址码,即要读的地址 while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))); //等待主机发送过程完成 I2C_GenerateSTART(I2Cx, ENABLE); //I2C开始发送 while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))); //设置主机模式 I2C_Send7bitAddress(I2Cx,EE24cBlockSelect,I2C_Direction_Receiver); //设置从机地址,并设置主机为接收模式 while(!(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))); //确认该过程完成 while(NumToRead) { if(NumToRead==1) { I2C_AcknowledgeConfig(I2Cx, DISABLE); //关闭I2C的应答功能 I2C_GenerateSTOP(I2Cx, ENABLE); //发送结束信息 } if((I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED))) //如果接收到信息了 { *pBuffer = I2C_ReceiveData(I2Cx); //把接收到的数据 填进缓冲区当中 pBuffer++; NumToRead--; } } I2C_AcknowledgeConfig(I2Cx, ENABLE); //开启主机I2C的应答功能 } ACK是I2C一个很重要的概念,就是握手信息。 已经融合到了上述代码中。 前面的功能就可以完成I2C的写和读, 完善代码当然还包括成块读写的代码, 这些内容将打包起来,给读者慢慢体会。 上传 这部分功能函数的.c和.h文件, 大家尝试加到自己的工程里去! PS:这两个文件中没有IO的设置,大家需要自己设置IO,代码已经在上面列出。吼吼 |
sw笨笨的STM32笔记之九:打断它来为我办事,EXIT (外部I/O中断)应用
a) 目的:跟串口输入类似,不使用中断进行的IO输入效率也很低,而且可以通过EXTI插入按钮事件,本节联系EXTI中断。 b) 初始化函数定义: void EXTI_Configuration(void); //定义IO中断初始化函数 c) 初始化函数调用: EXTI_Configuration();//IO中断初始化函数调用简单应用: d) 初始化函数: void EXTI_Configuration(void) { EXTI_InitTypeDef EXTI_InitStructure; //EXTI初始化结构定义 EXTI_ClearITPendingBit(EXTI_LINE_KEY_BUTTON);//清除中断标志 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);//管脚选择 GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource5); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6); EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//事件选择 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//触发模式 EXTI_InitStructure.EXTI_Line = EXTI_Line3 | EXTI_Line4; //线路选择 EXTI_InitStructure.EXTI_LineCmd = ENABLE;//启动中断 EXTI_Init(&EXTI_InitStructure);//初始化 } e) RCC初始化函数中开启I/O时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); GPIO初始化函数中定义输入I/O管脚。 //IO输入,GPIOA的4脚输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 f) 在NVIC的初始化函数里面增加以下代码打开相关中断: NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQChannel; //通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//占先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //启动 NVIC_Init(&NVIC_InitStructure); //初始化 g) 在stm32f10x_it.c文件中找到void USART1_IRQHandler函数,在其中添入执行代码。一般最少三个步骤:先使用if语句判断是发生那个中断,然后清除中断标志位,最后给字符串赋值,或做其他事情。 if(EXTI_GetITStatus(EXTI_Line3) != RESET) //判断中断发生来源 { EXTI_ClearITPendingBit(EXTI_Line3); //清除中断标志 USART_SendData(USART1, 0x41); //发送字符“a” GPIO_WriteBit(GPIOB, GPIO_Pin_2, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_2)));//LED发生明暗交替 } h) 中断注意事项: 中断发生后必须清除中断位,否则会出现死循环不断发生这个中断。然后需要对中断类型进行判断再执行代码。 使用EXTI的I/O中断,在完成RCC与GPIO硬件设置之后需要做三件事:初始化EXTI、NVIC开中断、编写中断执行代码。 |
sw笨笨的STM32笔记之十四:基本问题,来讨论一下软件架构
文章评论(0条评论)
登录后参与讨论