第10讲I2C
1、I2C简介
两线式串行总线,连接微控制器及外围设备。双向同步半双工。每个器件有唯一地址,工作在主模式或从模式,可以发送或接收。主控器能产生时钟信号,总线上开漏模式上拉。
2、I2C协议内容:空闲、开始、停止、应答、数据有效性、数据传输等
空闲:SDA和SCL都是高电平,则空闲。
开始:SCL为高期间,SDA由高到低跳变
停止:SCL为高期间,SDA由低到高跳变
应答:发送器每发送一个字节,就在第9时钟脉冲期间释放数据线,由接收器反馈一个应答信号。有效应答信号为低电平,非应答信号为高电平。对有效应答ACK要求:接收器在第9个SCL信号前的低电平期间拉低SDA,确保SCL的高电平期间为稳定的低电平。
数据有效性:SCL上升沿时采集数据,且在SCL下降沿前SDA上的数据必须稳定。
数据传输:I2C总线上传输的每个字节均为8位,首先传输MSB,每次启动一次I2C总线,其后传输的字节数是没有限制的。每传输一个字节后都必须由接收器回应一个应答(ACK或NACK)。SDA上,第一帧为寻址字节,包括7位被控器的地址和1位读写方向位,接着是被控器的应答位。紧接着是主控器和被控器的数据传输和应答,传输结束后,主控器要发停止信号。
3、用GPIO口软件仿真I2C方法
void IIC_Init(void){//初始化 GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); //GPIOB8、9初始化 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); IIC_SCL=1; IIC_SDA=1; } void IIC_Start(void){//开始 SDA_OUT(); //sda线输出 IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0; //START:SCL高,SDA从高到低跳变 delay_us(4); IIC_SCL=0; //钳住I2C总线,准备发送或接收数据 } void IIC_Stop(void){//IIC结束信号 SDA_OUT(); //sda线输出 IIC_SCL=0; IIC_SDA=0; delay_us(4); IIC_SCL=1; //STOP:SCL高,SDA从低到高跳变 IIC_SDA=1; delay_us(4); } u8 IIC_Wait_Ack(void){//等待应答 u8 ucErrTime=0; SDA_IN(); //SDA线输入 IIC_SDA=1; delay_us(1); IIC_SCL=1; delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250){ IIC_Stop(); return 1; } } IIC_SCL=0; return 0; } void IIC_Ack(void){//应答 IIC_SCL=0; SDA_OUT(); IIC_SDA=0;//ACK delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } void IIC_NAck(void){//非应答 IIC_SCL=0; SDA_OUT(); IIC_SDA=1;//NACK delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } void IIC_Send_Byte(u8 txd){//发送数据 u8 t; SDA_OUT(); IIC_SCL=0; //拉低时钟准备发送 for(t=0;t<8;t++){ IIC_SDA=(txd&0x80)>>7; //先发送MSB(最高有效位) txd<<=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; //时钟低电平才允许数据改变 delay_us(2); } } u8 IIC_Read_Byte(unsigned char ack) {//接收数据 unsigned char i,receive=0;//接受字节 SDA_IN(); //SDA线输入 for(i=0;i<8;i++ ){ IIC_SCL=0; delay_us(2); IIC_SCL=1;//上升沿采集数据 receive<<=1; if(READ_SDA) receive++; delay_us(1); } if (!ack) IIC_NAck(); //发送NACK else IIC_Ack(); //发送ACK return receive; }
第11讲SPI
SPI2/SPI3挂载在APB1上,其余挂载在APB2上。
1、SPI简介
高速、全双工、同步串行通信总线,只占4根线,任何时候只能有一个主机和从机通信。发送数据时必然接收到数据
2、SPI从设备的选择方法
SPI_CR1的SSM位设置硬件或软件管理NSS(从器件选择),为1时软件管理,NSS由SPI_CR1寄存器的SSI位驱动。为0时是硬件管理,根据SPI_CR2的SSOE位控制模式。
3、SPI主要寄存器
SPI_CR1:控制寄存器1.
SPI_CR2:控制寄存器2,与中断相关。
SPI_SR:位1TXE,发送缓冲区空。位0RXNE,接收缓冲区非空。位7BSY,忙标志。
SPI_DR:数据寄存器。物理上分两个缓冲区:发送缓冲区和接收缓冲区。
SPI_I2SCFGR:I2S模式配置。
4、SPI常用库函数
void SPI_Init(SPI_TypeDef* SPIx,SPI_InitTypeDef* SPI_InitStruct); void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);//使能 void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);//设置中断 void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState); void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);//传送数据 uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);//接收数据 void SPI_DataSizeConfig(SPI_TypeDef* SPIx,uint16_t SPI_DataSize); FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG); void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);//清除状态标志位 ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);//获取中断标志位 void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
5、如何判断SPI一个数据发送完成、如何判断SPI接收到一个数据
数据先于时钟,选择第一边沿采集。数据时钟同时,选择第二边沿。
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
//一个数据没发完,等待发送完成
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
//没有接受到数据,等待接收到1帧数据
SPI_I2S_SendData(SPI1,ch);
//库函数方式发送数据
SPI1->DR=ch;
//寄存器方式
第12讲ADC
挂载在APB2上
1、STM32F4 ADC简介:特点、性能、分辨率、数据对齐、通道数量等
特点:模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。 在转换结束、注入转换结束以及发生模拟看门狗溢出事件时产生中断。可独立设置各通道采样时间 外部触发器选项,可为规则转换和注入转换配置极性
性能:ADC电源要求:全速运行2.4V ~ 3.6V,慢速运行1.8V
分辨率:可以配置为12位、10位、8位或6位。
对齐:左对齐或右对齐的方式存储在16位数据寄存器中。
通道数量:3个AD转换器,每个AD转换器有16个外部通道,共24个外部通道。3个内部通道(ADC1)
2、规则通道、注入通道的概念
规则通道组:相当于正常运行的程序,最多16个通道。
注入通道组:相当于中断,最多4个通道。
3、工作模式:单次、连续、扫描、间断
单次:将ADC_CR2的CONT位置0,则处于单次转换模式。该模式下ADC仅执行一次转换,然后ADC停止。此模式下,可通过以下方式启动转换:将ADC_CR2的SWSTART位置1(规则通道),将JSWSTART位置1(注入通道)、外部触发。所选通道转换结束后结果保存在16位ADC_DR(ADC_JDR1)中EOC(JEOC)标志置1.EOCIE(JEOCIE)位置1时将产生中断
连续:将ADC_CR2的CONT位置1,则处于连续转换模式。该模式下ADC结束一个转换后立即启动一个新的转换。此模式下,可通过以下方式启动转换:将ADC_CR2的SWSTART位置1、外部触发。每次转换结束后转换结果存储在16位 ADC_DR中.EOC标志置1;EOCIE位置1时将产生中断
扫描:扫描一组通道。将ADC_CR1中的SCAN位置1选择扫描模式。ADC会扫描在ADC_SQRx寄存器(规则通道)或ADC_JSQR寄存器(注入通道)中选择的所有通道。为组中的每个通道都执行一次转换。自动注入模式下需要禁止外部事件触发,EXTEN/JEXTEN[1:0]=00
间断:将ADC_CR1的DISCEN位置1使能此模式。该模式可用于转换n(n≤8)个转换的短序列,该短序列是在ADC_SQRx中选择的转换序列的一部分。出现外部触发时,将启动在ADC_SQRx寄存器中选择的接下来n个转换,直到序列中的所有转换均完成为止。
4、转换时间计算
5、如何启动ADC
ADC启动可由软件控制,将SWSTART/JSWSTART的位置1开始转换。也可由外部事件触发。
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC_Cmd(ADC1, ENABLE);
//使能ADC1
void ADC_SoftwareStartConv(ADC_TypeDef* ADCx)
ADC_SoftwareStartConv(ADC1);
//软件启动ADC1规则组转换
ADC1->CR2|=1<<30
//寄存器方式
ADC_SoftwareStartInjectedConv(ADC1);
//注入组
ADC1->CR2|=1<<22
//寄存器方式
6、如何读取转换结果(注入组、规则组,库函数、寄存器)
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
return ADC_GetConversionValue(ADC1);
//获取ADC1转换结果
u16 Get_Adc(u8 ch){ ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_480Cycles ); ADC_SoftwareStartConv(ADC1); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待ADC1规则组转换结束 while((ADC1->SR&(1<<1))==0)//等待ADC1规则组转换结束,寄存器 return ADC_GetConversionValue(ADC1);//规则组读取 return ADC1->DR;//规则组读取,寄存器 return ADC_GetInjectedConversionValue(ADC1,ADC_InjectedChannel_1);//注入组读取 return ADC1->JDR1;//注入组读取,寄存器 }
7、常用寄存器
ADC_SR
:状态寄存器,STRT和JSTRT中断无关,其余位中断有关。
ADC_CR1
:控制寄存器。
ADC_CR2
:控制寄存器。CONT
位=1位连续,CONT
=0为单次
ADC_SMPR1/2
:采样时间寄存器。每个通道可以设置不同采样时间。
ADC_JOFRx
:注入通道数据偏移寄存器,转换注入通道时从原始转换数据减去偏移量
ADC_HTR
:上阈值寄存器。
ADC_LTR
:下阈值寄存器。
ADC_SQR1/2/3
:定义通道顺序。
ADC_JSQR
:注入通道顺序
ADC_DR
:数据寄存器。AD转换结果。规则组数据寄存器低16位有用,可设置左右对齐。
ADC_JDR1/2/3/4
:注入通道转换结果。
ADC_CSR
:3个ADC公用的状态寄存器,只读。
ADC_CCR
:公用控制寄存器,可以读写。
8、常用库函数
void ADC_CommonInit(ADC_CommonInitTypeDef* ADC_CommonInitStruct);//通用控制寄存器ADC_CCR初始化 void ADC_Init(ADC_TypeDef* ADCx,ADC_InitTypeDef* ADC_InitStruct);//ADC_CR初始化 void ADC_DeInit(ADC_TypeDef* ADCx)//取消初始化 void ADC_Cmd(ADC_TypeDef* ADCx,FunctionalState NewState); void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT,FunctionalState NewState);//中断配置 void ADC_SoftwareStartConv(ADC_TypeDef* ADCx);//开始转换 void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);//规则组通道配置 uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
9、ADC数据采集程序,单通道连续转换
整体流程
RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); GPIO_Init();//1.开启PA口时钟和ADC1时钟,设置PA1(ADC1通道1)为模拟输入 RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE); RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE);//2.复位ADC1 ADC_CommonInit();//3.初始化ADC_CCR寄存器,同时设置ADC1分频因子 void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct); //4.初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息 ADC_RegularChannelConfig();//5.配置规则通道参数 ADC_Cmd(ADC1, ENABLE);//6.使能ADC1 ADC_SoftwareStartConv(ADC1);//7.软件开启转换 ADC_GetConversionValue(ADC1);//8.等待转换完成读取ADC NVIC_Init(); ADC_ITConfig(ADC1,ADC_IT_EOC,ENABLE); ADC_IRQHandler();//9.如采用中断方式,还需
void Adc_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStructure); RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,ENABLE); RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1,DISABLE); ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; ADC_CommonInitStructure.ADC_Prescaler=ADC_Prescaler_Div4 //不能超过36MHz ADC_CommonInit(&ADC_CommonInitStructure); ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_ExternalTrigConvEdge=ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_Cmd(ADC1, ENABLE); }
第13讲DAC
挂载在APB1上
1、STM32F4 DAC简介:特点、数据格式、分辨率等
特点:12位数字输入,电压输出型的DAC
数据格式/分辨率:
8 位右对齐:必须将数据加载到 DAC_DHR8Rx [7:0] 位(存储到DHRx[11:4] 位)。
12 位左对齐:必须将数据加载到 DAC_DHR12Lx [15:4] 位(存储到DHRx[11:0] 位)。
12 位右对齐:必须将数据加载到 DAC_DHR12Rx [11:0] 位(存储到DHRx[11:0] 位)。
2、输出电压计算方法
VREF+为参考电压输入,DOR为数据输出寄存器的值
3、主要寄存器
DAC_CR
:控制寄存器
DAC_SWTRIGR
:软件触发寄存器
DAC_DHR yyyx
:数据输入寄存器,如DAC_DHR12R1
,DAC_DHR12L1
,DAC_DHR8R1
DAC_DORx
:数据输出寄存器,通道1的数据为DAC_DOR1
,通道2的数据为DAC_DOR2
DAC_SR
:状态寄存器
4、常用库函数
void DAC_Init(uint32_t DAC_Channel,DAC_InitTypeDef* DAC_InitStruct);
//初始化
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
//使能
void DAC_SetChannel1Data(uint32_t DAC_Align, uint16_t Data);
//写入DHR数据
uint16_t DAC_GetDataOutputValue(uint32_t DAC_Channel);
//回读输出数据
5、如何启动DAC
void DAC_Cmd(uint32_t DAC_Channel, FunctionalState NewState);
//CR对应位置1
定时器 TRGO 输出或外部中断线 9 上检测到上升沿时,DAC_DHRx中的数据即会加载到DAC_DORx 中。再经过三个APB1 周期,DAC_DORx 将会得到更新。
如果选择软件触发,一旦 SWTRIG 位置 1, 转换即会开始.DAC_DHRx 加载到DAC_DORx 后,SWTRIG 即由硬件复位。
6、如何将一个数据送DAC转换(库函数、寄存器)
DAC_SetChannel1Data(DAC_Align_12b_R, 500);
//将500送入,库函数
DAC->DHR12R1 = 500;
//将500送入,寄存器
num=DAC->DOR1;
7、DAC产生相应波形输出的编程
void Dac1_Init(void){ GPIO_InitTypeDef GPIO_InitStructure; DAC_InitTypeDef DAC_InitType; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; GPIO_Init(GPIOA, &GPIO_InitStructure); DAC_InitType.DAC_Trigger=DAC_Trigger_None; DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None; DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0; DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; DAC_Init(DAC_Channel_1,&DAC_InitType); DAC_Cmd(DAC_Channel_1, ENABLE); DAC_SetChannel1Data(DAC_Align_12b_R, 0); } void Dac1_Set_Vol(u16 vol){//设置通道1输出电压 double temp=vol; temp/=1000; temp=temp*4096/3.3;//vol:0~3300,代表0~3.3V DAC_SetChannel1Data(DAC_Align_12b_R,temp);//12位右对齐数据格式DAC值 } int main(void){ u16 adcx; float temp; u8 t=0; u16 dacval=0; u8 key; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); delay_init(168); uart_init(115200); LED_Init(); Adc_Init(); KEY_Init(); Dac1_Init(); DAC_SetChannel1Data(DAC_Align_12b_R,dacval); while(1){ t++; key=KEY_Scan(0); if(key==WKUP_PRES){ if(dacval<4000) dacval+=200; DAC_SetChannel1Data(DAC_Align_12b_R, dacval); }else if(key==KEY1_PRES) { if(dacval>200) dacval-=200; else dacval=0; DAC_SetChannel1Data(DAC_Align_12b_R, dacval); } if(t==10||key==KEY1_PRES||key==WKUP_PRES) { adcx=DAC_GetDataOutputValue(DAC_Channel_1); printf(“DAC寄存器的值是:%d\n”,adcx); temp=(float)adcx*(3.3/4096); printf(“DAC的输出电压是:%f\n”,temp); adcx=Get_Adc_Average(ADC_Channel_5,10); temp=(float)adcx*(3.3/4096); printf(“ADC采集到的电压是:%f\n”,temp); LED0=!LED0; t=0; } delay_ms(10); } }
第14讲 RTOS
1、理解嵌入式应用软件的设计的两种方式
前后台系统方式(裸机方式):没有嵌入式操作系统支持,直接操作裸机,在裸机上写程序,比如用51单片机进行设计,基本就没有操作系统的概念。通常把程序分为两部分:前台系统和后台系统。
RTOS方式:在嵌入式操作系统的支持下进行应用程序设计,由嵌入式操作系统进行内存、任务、外围设备等管理,减轻开发人员负担。
2、对RTOS有两个基本要求:功能正确、时间正确,硬实时、软实时
能使计算机系统及时响应外部事件的请求,并能及时控制所有实时设备与实时任务协调运行,且能在规定的时间内完成对事件的处理,逻辑或功能正确,时间正确.
硬实时操作系统:必须在极严格的时间内完成任务
软实时操作系统:对完成任务的截止时间要求不是十分严格,对超时有一定的容忍度
3、RTOS内核的两种类型
不可剥夺型内核(合作型多任务内核):总是优先级别高的任务最先获得CPU使用权,每个任务必须能主动放弃CPU使用权
可剥夺型内核:CPU总是运行多个任务中优先级别最高的任务,高级别任务可以剥夺正在运行任务的CPU使用权
4、任务的组成
由三部分组成:任务堆栈、任务控制块和任务函数
任务堆栈:上下文(任务)切换的时候用来保存任务的工作环境,即STM32的内部寄存器值
任务控制块:用来记录任务的各个属性
任务函数:由用户编写的任务处理代码
5、μCOSIII的特点
μC/OS III是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。μC/OS III提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。
μC/OS III是用C和汇编写的,其中绝大部分都是用C语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,结构简洁,可读性很强。