谨以此文和我去年前的一篇蓝桥杯单片机的教程构成电子类的青铜双壁.
国信长天单片机竞赛训练之原理图讲解及常用外设原理(遗失的章节-零)_昊月光华的博客-CSDN博客
目录
*串口的DMA+空闲中断不定长接受任意类型的数据(高效且简洁)

注意:

- int fputc(int ch,FILE * f )
- {
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1,0xff);
-
- return ch;
-
- }
- u8 LEDINDEX[]= {0X00,1<<0,1<<1,1<<2,1<<3,1<<4,1<<5,1<<6,1<<7};
- u8 LEDDT[]={0,0,0,0,0,0,0,0,0};
- void led_display(u8 ds)
- {
- HAL_GPIO_WritePin(GPIOC,0xff<<8,GPIO_PIN_SET);
- HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
- HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
-
-
- HAL_GPIO_WritePin(GPIOC,ds<<8,GPIO_PIN_RESET);
- HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
- HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
-
- }
- void led_scan(){
- int ds = 0;
- for(int i = 0 ;i < 4;i++)
- {
- ds|=LEDINDEX[LEDDT[i]];
-
- }
- led_display(ds);
- }
运行期间只需要更改LEDDT的值就行,LEDDT没有顺序,表示最多同时点亮的灯的数目。
(状态机,假设同时只有一个按键按下,无论长按还是短按都只判断按下,记作一次)这个代码只能一次性判断一个按键按下,无法对同一时刻多个按键按下进行判断,一般这个代码就够用了)
使用定时器7,配置为20ms进入一次回调函数。这种我认为是日常中比较常用的按键操作,因为以前对51单片机按键扫描的记忆,故回顾了下。
- #define ReadB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
- #define ReadB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
- #define ReadB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
- #define ReadB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
- u8 key_state= 0;
- u8 rkey = 0;
- u8 key = 0;
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
-
- if(htim->Instance == TIM2)
- {
- LedFlusht++;
- if(time++ == 1000)
- {
- time = 0;
- LEDDT[1] = LEDDT[1]?0:1;
-
-
- }
-
-
-
- }
- else if(htim->Instance == TIM7)
- {
-
- //执行按键扫描
- switch(key_state)
- {
-
- case 0:
- if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
- {
- key_state = 1;
-
- }
- break;
- case 1:
- if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
- {
- key_state = 2;
- if(!ReadB1) key = 1;
- else if(!ReadB2) key=2;
- else if(!ReadB3) key=3;
- else if(!ReadB4) key =4;
- rkey = key;
-
- }
- else
- {
-
- key_state = 0;
- key = 0;
-
- }
- break;
- case 2:
- rkey = 0;//在只需要判断按下之后,不管长按短按(有没有松开)都试做按下一次
- if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
- {
-
- }
- else
- {
- key = 0;
- key_state= 0;
-
- }
- break;
-
-
- }
-
-
- keyAction();
-
-
-
-
- }
-
-
- }
原理:把按键定义成一个结构体变量:
-
- typedef struct Key{
- u16 keytime;
- u8 keystate;
- bool sflag;
- bool lflag;
- bool state;
-
- }Key;
按键扫描:(按下小于500ms属于短按,大于500ms属于长按) ,按键扫描函数单独用一个定时器,每10ms产生一次中断去执行,这也就是为什么keytime+=10的原因.
- #define ReadB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
- #define ReadB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
- #define ReadB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
- #define ReadB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
-
- void key_scan()
- {
-
- keys[0].state = READB1;
- keys[1].state = READB2;
- keys[2].state = READB3;
- keys[3].state = READB4;
-
- for(int i = 0 ; i < 4 ;i++)
- {
- switch(keys[i].keystate)
- {
-
- case 0:
- if( !keys[i].state){
- keys[i].keystate =1;
-
- }
- break;
- case 1:
- if(!keys[i].state){
- keys[i].keystate =2;
-
- }
- else
- keys[i].keystate = 0;
- break;
- case 2:
- if(!keys[i].state)
- {
-
- keys[i].keytime+=10;
-
- }
- else
- {
- if(keys[i].keytime > 500)
- {
- keys[i].lflag = 1;
- }
- else
- keys[i].sflag = 1;
-
-
- keys[i].keytime = 0;
- keys[i].keystate=0;
-
-
-
- }
- break;
- default:
- break;
-
-
- }
- }
- }
-
- //串口测试代码
- void keywork(void)
- {
-
- if(keys[0].sflag)
- {
-
- printf("0\r\n");
- keys[0].sflag = 0;
-
- }
- else if(keys[0].lflag)
- {
-
- printf("long 0\r\n");
- keys[0].lflag = 0;
- }
- if(keys[1].sflag)
- {
-
- printf("1\r\n"); keys[1].sflag = 0;
-
- }
-
-
- else if(keys[1].lflag)
- {
-
- printf("long 1\r\n");
- keys[1].lflag = 0;
- }
-
-
-
-
-
-
- if(keys[2].sflag)
- {
-
- printf("2\r\n");
- keys[2].sflag = 0;
- }
- if(keys[3].sflag)
- {
-
- printf("3\r\n");
- keys[3].sflag = 0;
-
- }
-
-
-
- }
逻辑处理: 处理完后sflag (短按标志)或lflag(长按标志)记得清零处理。
HAL_GetTick() 函数是 STM32 微控制器 HAL(硬件抽象层)库提供的函数。它返回当前以毫秒为单位的节拍计数。默认情况下,系统定时器每1ms生成一个中断以更新节拍计数。因此,在大多数情况下,每个节拍的速率为1ms。
此函数可用于实现基于时间的操作,例如延迟、超时和任务调度。通过比较当前节拍计数与之前保存的值,可以确定自那个事件以来经过的时间。
例如,要创建100ms的延迟,您可以使用 HAL_GetTick() 保存当前节拍计数,然后在循环中等待,直到保存的节拍计数和当前节拍计数之间的差达到100ms。
uint32_t startTick = HAL_GetTick(); while ((HAL_GetTick() - startTick) < 100);
请注意,当节拍计数达到其最大值(0xFFFFFFFF)后,它会绕回,并且在计算长时间内经过的时间时必须考虑到这一点。
我们使用DHT11,DS18B20需要用到的us级延时就需要通过systick来实现。
HAL_GetTick(); 获取当前节拍.默认是1ms产生1个tick(节拍),则systick的计数值1ms发生溢出产生中断,
systick的默认中断函数:(每次中断溢出则增加节拍 ”Tick“)
Systick的主频:
SysTick定时器的主频取决于所使用的系统时钟(HCLK)频率和它的分频系数。在STM32微控制器中,SysTick定时器的时钟源可以配置为HCLK/8或HCLK。
如果SysTick定时器的时钟源被配置为HCLK/8,则SysTick定时器的主频将为HCLK/8。例如,如果HCLK的频率为72MHz,则SysTick定时器的主频将为9 MHz。
如果SysTick定时器的时钟源被配置为HCLK,则SysTick定时器的主频将为HCLK。例如,如果HCLK的频率为72MHz,则SysTick定时器的主频将为72 MHz。
需要注意的是,SysTick的主频越高,计数器溢出的时间间隔就越短,因此可以获得更高的精度和分辨率。但是,SysTick的主频和计数器位数的组合也会影响 SysTick 的最大定时周期。
systick的时钟源一般被设置为AHB总线上的时钟频率,比如常见的80MHZ.

配置systick的时钟频率 HAL_SYSTICK_CLKSourceConfig()
两种选择 HCLK 或 HCLK/8

溢出时间判断:SYSTICK->LOAD的装载值为79999,所以:
产生一次中断的时间为:1/(80M-1)*(79999+1) =1/10^3 = 1ms

默认的SYSTICK中断函数:
- /**
- * @brief This function handles System tick timer.
- */
- __weak void SysTick_Handler(void)
- {
- /* USER CODE BEGIN SysTick_IRQn 0 */
-
- /* USER CODE END SysTick_IRQn 0 */
- HAL_IncTick();
- /* USER CODE BEGIN SysTick_IRQn 1 */
-
- /* USER CODE END SysTick_IRQn 1 */
- }
-
- /**
- * @brief This function is called to increment a global variable "uwTick"
- * used as application time base.
- * @note In the default implementation, this variable is incremented each 1ms
- * in SysTick ISR.
- * @note This function is declared as __weak to be overwritten in case of other
- * implementations in user file.
- * @retval None
- */
- __weak void HAL_IncTick(void)
- {
- uwTick += uwTickFreq;
- }
把SYSTICK做定时用,重写systick的中断函数.(节省定时器,一般情况下,我们都不会这样去做,因为OS会把它作为时基,而我们的一些ms级延时也要得益改变systick的频率得到)
此时需要把原来的SysTick_Handler标记为weak。不好的地方在于每次生成代码都需要改.
这一点纯属当拓展知识去用,知道可以这么干就可以了.
- void SysTick_Handler(void)
- {
-
- HAL_IncTick();
- static uint32_t ick = 0; // 定义静态变量tick,记录自系统启动以来经过的毫秒数
- ick++; // 每次中断触发时tick值加1
- if (ick == 1000) { // 如果tick值等于1000,则表示已经过了1秒钟
- // TODO: 执行每秒钟需要执行的任务
- ick = 0; // 将tick值重置为0,开始新的计数
- printf("hello world\r\n");
- }
- }
比赛实际上用单通道就可以,每次采样需要指定adc的通道.
STM32基于hal库的adc以DMA的多通道采样以及所遇问题解决_stm32 hal adc dma_昊月光华的博客-CSDN博客
(记得数据宽度设置为1个字节 8位)
若是hal库的版本并不是特别的新,那么还是用串口接受中断+空闲中断吧,我这边测试1.3的固件包是可以用DMA的那个空闲中断函数 ,如:
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,Rx1Buf,200); //串口1开启DMA接受
基于HAL库的STM32的串口DMA发送数据(解决只发送一次数据)及DMA+空闲中断接受数据_hal 串口dma发送_昊月光华的博客-CSDN博客
对输入的脉冲进行捕获,算出求频率和占空比.设置定时器的频率的1M用来对输入的脉冲进行计数,假设输入脉冲一个周期的计数值为t(从一个脉冲的上升沿到下一个脉冲的上升沿).则频率计算为 1000000 / t .
比如对PA15信号发生器.

配置(PA15接脉冲发生器测出来的占空比大概为50%。

- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
- {
-
- static u16 t = 0;
- static u16 d = 0;
- if(htim->Instance == TIM2)
- {
- if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
- {
- LEDDT[0]=1;
- t = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1)+1;
- freq = 1000000 / t;
- duty = (float)d/t*100;
-
- }
-
- else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
- {
- LEDDT[1]=2;
- d = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2)+1;
- }
-
-
- }
- }
通过按键打印

pwm的频率 = 定时器的频率/autoreload的值.其中定时器的频率 = 时钟频率/分频系数.
之前参加过蓝桥杯单片机的相信有过经历:用定时器去实习pwm波:
记得我们是如何实现的?
假设定时器的频率是1M.每1us计数值加1,若设置为1000个计数值后溢出则产生一次中断,则1ms进入一次中断,在中断函数内,我们让其定义一个变量,让其加到200后归0,其中前100ms设置为高电平,后100ms设置为低电平,则周期为200ms,频率为1/200ms=5HZ,占空比为50%。(假设有效电平为高电平).
再回到STM32来,这里的autoreload就是计数值,一个计数的时间是1/定时器的频率。则PWM的频率为1 / [autoreload*1/定时器的频率) ] = 定时器的频率/ autoreload .证明完毕.
我们通过更改autoreload的值去动态的更改pwm的频率,需要注意的是当autoreload的值发生改变,需要重新去计算占空比。 占空比 = TIM15->CCR1 /autoreload .
- //改变PWM的频率且稳定占空比保持不变 以TIM15->CCR1为例
- void changePwmFreqAndkeepDuty(float duty,u16 autoloadval)
- {
-
- extern TIM_HandleTypeDef htim15;
- TIM15->CCR1 = duty*autoloadval;
- printf("new freq: %d\r\n",10000/autoloadval);
- __HAL_TIM_SetAutoreload(&htim15,autoloadval-1);
- HAL_TIM_GenerateEvent(&htim15, TIM_EVENTSOURCE_UPDATE);
-
-
- }
通过按键触发更改
按键触发的demo
- if(keys[0].sflag)
- {
-
- static u8 t =0;
- t++;
- static bool flag = false;
- if( t == 100) t=0;
-
- void changePwmFreqAndkeepDuty(float duty,u16 autoloadval);
-
- flag =!flag;
- if(flag) changePwmFreqAndkeepDuty(0.8,50);
- else changePwmFreqAndkeepDuty(0.5,100);
-
- printf("0\r\n");
- keys[0].sflag = 0;
-
- }

相信看到这,PWM的产生原理已经很清楚了,用cubeMX配置将会更加清楚.

一个定时器可以配置多个通道产生多组PWM波.
开启PWM波:
HAL_TIM_PWM_Start_IT(&htim15,TIM_CHANNEL_1);
更改PWM的值(如TIM15) TIM15->CCR1 =XXX (表示TIM15的通道1)
需要注意在读数据时,需要主机发送应答信号表示。以及需要初始化.
I2CInit(void);


- //EEPROM读写代码
- void eeprom_write(unsigned char *pucBuf, unsigned char ucAddr, unsigned char Size)
- {
- I2CStart();
- I2CSendByte(0xa0);
- I2CWaitAck();
-
- I2CSendByte(ucAddr);
- I2CWaitAck();
-
- while(Size--)
- {
- I2CSendByte(*pucBuf++);
- I2CWaitAck();
- }
- I2CStop();
-
- }
-
- void eeprom_read(unsigned char *pucBuf, unsigned char ucAddr, unsigned char Size)
- {
- I2CStart();
- I2CSendByte(0xa0);
- I2CWaitAck();
-
- I2CSendByte(ucAddr);
- I2CWaitAck();
-
- I2CStart();
- I2CSendByte(0xa1);
- I2CWaitAck();
-
- while(ucNum--)
- {
- *pucBuf++ = I2CReceiveByte();
- if(Size)
- I2CSendAck();
- else
- I2CSendNotAck();
- }
- I2CStop();
- }
驱动代码比赛官方会给出.只需要自己写一个读取温度的函数就行.给出的驱动有个不稳定的因素,那就是delay_us()这个延时函数没有重写。驱动是以80M做的一个微秒级延时,当在更改系统时钟频率时则有大问题(因为指令周期会随着系统的时钟频率而变化!),同样的,DHT11的驱动也有这个问题.,这完全是让学生专注于与逻辑业务的实现,驱动能跑就行(记得设置系统主频为80M).
资源包给的延时函数:
- #define Delay_us(X) delay((X)*80/5)
-
- void delay(unsigned int n)
- {
- while(n--);
- }
跳线帽连接 P4 与 P3 的 TDQ进行连接.
main函数中调用初始化:
ds18b20_init_x();
读取温度
- float ds18b20_read(void)
- {
-
- unsigned char th,tl;
- unsigned short res;
- ow_reset();
- ow_byte_wr(0xcc);
- ow_byte_wr(0x44);
-
-
- ow_reset();
- ow_byte_wr(0xcc);
- ow_byte_wr(0xbe);
-
- tl = ow_byte_wr(0xff);
- th = ow_byte_wr(0xff);
-
- res=(th<<8)|tl;
-
- return res*0.0625;
-
-
-
- }
每次读取完后的下一次读取则会失败,这不是我们的问题。只需要得到在它正确读取时的值就行。

dht11比赛赛点资料包(14届)给的驱动代码只给了2个函数,两个改变DQ输入输出的函数。。。
dht11_hal.h
- #ifndef __DHT11_HAL_H
- #define __DHT11_HAL_H
-
- #include "stm32g4xx_hal.h"
-
- #define HDQ GPIO_PIN_7
-
-
-
-
-
- typedef struct {
-
- float temp;
- float humi;
-
- }dht11Data;
-
- void dht11Init(void);
- int Readdht11(void);
-
-
- #endif
dht11_hal.c
- #include "dht11_hal.h"
-
- #define OUTDQLOW HAL_GPIO_WritePin(GPIOA, HDQ, GPIO_PIN_RESET)
- #define OUTDQHIGH HAL_GPIO_WritePin(GPIOA, HDQ, GPIO_PIN_SET)
- #define READDQ HAL_GPIO_ReadPin(GPIOA,HDQ)
- dht11Data dht11;
-
- //
- static void usDelay(uint32_t us)
- {
- uint16_t i = 0;
- while(us--){
- i = 30;
- while(i--);
- }
- }
-
- //
- void outDQ(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
-
-
- GPIO_InitStructure.Pin = HDQ;
- GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
- GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
-
-
- }
-
- //
- void inDQ(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
-
- GPIO_InitStructure.Pin = HDQ;
- GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
- GPIO_InitStructure.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
- }
-
- //
- void dht11Init(void)
- {
- __HAL_RCC_GPIOA_CLK_ENABLE();
- outDQ();
- OUTDQHIGH;
- }
-
- //
- uint8_t recData(void)
- {
- uint8_t i,temp=0,j=220;
-
- for(i=0; i<8; i++){
-
- while(!HAL_GPIO_ReadPin(GPIOA,HDQ));
- usDelay(40);
- if(HAL_GPIO_ReadPin(GPIOA,HDQ))
- {
- temp=(temp<<1)|1;
- while(HAL_GPIO_ReadPin(GPIOA,HDQ)&&(j--));
- }
- else
- {
- temp=(temp<<1)|0;
- }
- }
- return temp;
- }
-
- //复位DHT11
- void DHT11_Rst(void)
- {
- outDQ(); //设置为输出
- OUTDQLOW;
- HAL_Delay(20); //拉低至少18ms
- OUTDQHIGH;
- usDelay(60); //主机拉高20~40us
- }
-
- //等待DHT11的回应
- //返回1:未检测到DHT11的存在
- //返回0:存在
- unsigned char DHT11_Check(void)
- {
- unsigned char re = 0;
- inDQ(); //设置为输入
- while (READDQ && re < 100) //DHT11会拉低40~80us
- {
- re++;
- usDelay(1);
- };
- if(re >= 100)return 1;
- else re = 0;
- while (!READDQ && re < 100) //DHT11拉低后会再次拉高40~80us
- {
- re++;
- usDelay(1);
- };
- if(re >= 100)return 1;
- return 0;
- }
-
-
- int Readdht11(void)
- {
-
- unsigned char buf[5];
- unsigned char i;
- DHT11_Rst();
- if(DHT11_Check() == 0)
- {
- for(i = 0; i < 5; i++)
- {
- buf[i] = recData();
- }
- if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
- {
- dht11.humi = buf[0];
- dht11.temp =buf[2];
-
- }
- }
- else return 1;
- return 0;
-
- }
-
使用三个引脚去驱动三位数码管,采用SPI通信的方式
RCLK是存储允许信号(Register Clock signal)。它控制数码管何时实际显示存储在内部寄存器中的数据。当RCLK信号拉低时,数码管会更新内部寄存器,存储要显示的数据。当RCLK信号拉高时,数码管会从内部寄存器中读取数据,并实际显示出来。
在其下降沿时开始发送数据
SER是段选通信号(Segment Enable Register)。它控制数码管的哪些段被选通发光。数码管是由多个发光二极管构成的,它包含a、b、c、d、e、f、g等7段,以及小数点段。要显示某个数字,需要选通对应数字的发光段。例如,要显示数字“2”,需要选通a、b、d、e、g几个段。
与数码管的通信逻辑: 每次在SCK为低电平时传输数码管的段选数据位,先传ds3,再ds2,最后ds1.传输的数据由SN74LS595N进行移位.

SN74LS595N芯片
SN74LS595N是一种8位移位寄存器,也称为串行输入并行输出(SIPO)移位寄存器。它可以将接收的串行数据转换成并行数据输出。该芯片的主要特征有:- 8位移位寄存器,用于存储输入的串行数据并并行输出- 三态输出,可以直接驱动LED或数码管段选通- 简单的串行数据输入和时钟输入- 高电平电源:5V该芯片常用于LED点阵的驱动或数码管的段选通驱动。使用此芯片可以大大简化外围电路,减少芯片数量。
关于为什么需要从第三位段选数据开始传输?
1. SN74LS595N是一种移位寄存器,它会将输入的串行数据依次存入8个寄存器位,并在RCLK信号的跳变沿将这8位数据并行输出。2. 数码管的段选通信号是并行的,依赖这8个输出的数据选择点亮相应段。3. 所以,第一组输入的8位数据会存入移位寄存器的最末8位,并第一个输出驱动第一个数码管。4. 然后第二组8位数据输入会将第一组数据移到更高8位,自己存入最末8位,并输出驱动第二个数码管。5. 以此类推,每输入一组8位数据,之前的输出数据会移位8位,新的8位数据输出驱动新的数码管。(seg信号->DH#1->DH#2)所以从第三位数码管的段选信号开始)
数码管的驱动以及定时动态扫描,只需要更改smgDT[ ] 数组内的值完成数码管的显示。
-
-
-
- #include "seg.h"
-
-
- //
- uc8 Seg7[17] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, 0x00};
-
- u8 SmgDT[] ={ 16,16,16};
- void SEG_Init(void)
- {
-
- GPIO_InitTypeDef GPIO_InitStruct ;
- __HAL_RCC_GPIOA_CLK_ENABLE();
- HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_RESET);
- GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- }
-
- //
- void SEG_DisplayValue(u8 Bit1, u8 Bit2, u8 Bit3)
- {
- u8 i = 0;
- u8 code_tmp = 0;
- RCLK_L;
- code_tmp = Seg7[Bit3];
-
- for(i = 0; i < 8; i++)
- {
- SCK_L;
- if(code_tmp & 0x80)
- {
- SER_H;
- }
- else
- {
- SER_L;
- }
-
- code_tmp = code_tmp << 1;
- SCK_L;
- SCK_H;
- }
-
- code_tmp = Seg7[Bit2];
- for(i = 0; i < 8; i++)
- {
- SCK_L;
- if(code_tmp & 0x80)
- {
- SER_H;
- }
- else
- {
- SER_L;
- }
-
- code_tmp = code_tmp << 1;
- SCK_L;
- SCK_H;
-
- }
-
- code_tmp = Seg7[Bit1];
- for(i = 0; i < 8; i++)
- {
- SCK_L;
- if(code_tmp & 0x80)
- {
- SER_H;
- }
- else
- {
- SER_L;
- }
-
- code_tmp = code_tmp << 1;
- SCK_L;
- SCK_H;
-
- }
-
- RCLK_L;
- RCLK_H;
- }
-
-
- void ScanDisplay(void)
- {
-
-
- extern u8 smgt;
- if(smgt > 153 ) SEG_DisplayValue(SmgDT[0],SmgDT[1],SmgDT[2]),smgt = 0;
-
- }
原理:通过读取adc的值(0-4096之间)来映射按键1到按键8。

状态机判断adc按键单击操作
- u8 getadcKey()
- {
-
- // 这里的值完全可以试出来,我也记不住
- if(madc2 < 10) return 1;
- if(madc2 < 800) return 2;
- if(madc2 < 1600) return 3;
- if(madc2 < 2000) return 4;
- if(madc2 < 2700) return 5;
- if(madc2 < 3300) return 6;
- if(madc2 < 3700) return 7;
- if(madc2 < 4000) return 8;
-
-
- return 0;
-
- }
-
-
- u8 readadckey()
- {
-
- u8 static keystate =0;
- u8 temp = 0;
- u8 val = 0;
- switch(keystate)
- {
-
- case 0:
- temp =getadcKey();
- if(temp)keystate = 1;
- break;
- case 1:
- temp =getadcKey();
- if(temp)keystate = 2,val = temp;
- else keystate =0;
- break;
- case 2:
- temp =getadcKey();
- if(!temp)keystate =0;
-
- break;
- default:
- break;
-
-
-
- }
-
-
- return val;
-
-
- }
采用IIC通信协议. 注意,之前用epprom用的也是IIC,三轴加速度传感器的IIC的两根线:SCL,SDA是PA4,PA5.
mems.c
- #include "i2c.h"
- #include "mems.h"
-
-
- s8 alz[3] ;
-
- //
- void LIS302DL_Write(unsigned char reg, unsigned char info)
- {
- I2CStart();
- I2CSendByte(0x38);
- I2CWaitAck();
- I2CSendByte(reg);
- I2CWaitAck();
- I2CSendByte(info);
- I2CWaitAck();
- I2CStop();
- }
-
- //
- uint8_t LIS302DL_Read(uint8_t address)
- {
- unsigned char val;
- I2CStart();
- I2CSendByte(0x38);
- I2CWaitAck();
-
- I2CSendByte(address);
- I2CWaitAck();
-
- I2CStart();
- I2CSendByte(0x39);
- I2CWaitAck();
- val = I2CReceiveByte();
- I2CSendNotAck();
- I2CStop();
-
- return(val);
- }
-
- //
- s8 *Lis302DL_Output(void)
- {
- if((LIS302DL_Read(0x27) & 0x08) != 0)
- {
-
- alz[0] = (LIS302DL_Read(0x29)); //x
- alz[1] = (LIS302DL_Read(0x2B)); //y
- alz[2] = (LIS302DL_Read(0x2D)); //z
- }
-
- return alz;
- }
-
- //
- void LIS302DL_Config(void)
- {
- //Power up (100Hz data rate)
- LIS302DL_Write(CTRL_REG1, 0x47);
- //HP filter bypassed
- // LIS302DL_Write(CTRL_REG2, 0x00);
- // //FF_WU 1 interrupt send to INT Pad1(open drain) active low
- // LIS302DL_Write(CTRL_REG3, 0xC1);
- // //threshold :0.5 g
- // LIS302DL_Write(FF_WU_THS_1, 0x28);
- // //filter :200ms @100Hz
- // LIS302DL_Write(FF_WU_DURATION_1, 40);
- // //ZLIE event
- // LIS302DL_Write(FF_WU_CFG_1, 0x10);
- }
-
- //
- uint8_t LIS302DL_Check(void)
- {
- if(LIS302DL_Read(0x0f))
- {
- return 1;
- }
- else
- {
- return 0;
- }
- }
-
mems.h
- #ifndef __MEMS_H
- #define __MEMS_H
-
- #include "main.h"
-
- #define CTRL_REG1 0x20
- #define CTRL_REG2 0x21
- #define CTRL_REG3 0x22
- #define FF_WU_THS_1 0x32
- #define FF_WU_DURATION_1 0x33
- #define FF_WU_CFG_1 0x30
- #define STATUS_REG 0x27
-
-
- uint8_t LIS302DL_Check(void);
- void LIS302DL_Config(void);
- s8 *Lis302DL_Output(void);
-
- #endif
-

原理: 把RP7(电位器)处得到的电压值与3处的电压值进行比较器输出得到DO.AO则直接进行adc采样再数模转换得到电压。
DO:读取数字量,非常简单,直接读取PA3的引脚电平得出。通过转到电位器R7判断是否读取正确。
- if(!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) )
- {
- LCD_DisplayStringLine(Line7, (u8 *)" DO:High ");
- }
- else
- {
- LCD_DisplayStringLine(Line7, (u8 *)" DO:Low ");
- }
AO:读取模拟量,本质就是读取adc的值.


这里用到的是同一个adc的两个通道,完全可以用我上面的adc的dma的多通道采集实现.扩展板给的例程代码相当于每次都初始化一遍adc,然后再单通道采集。这种方式好处在于易于理解,而用adc的dma多通道则方便多了。
给出非DMA的单通道采集,当多通道使用则每次更改adc的配置参数并重新初始化adc.
- u16 getadc(u32 ch){
-
- ADC_ChannelConfTypeDef _adc = { 0 } ;
- _adc.Channel=ch;
- _adc.Rank=1;
- _adc.Rank = ADC_REGULAR_RANK_1;
- _adc.SamplingTime=ADC_SAMPLETIME_640CYCLES_5;
- _adc.SingleDiff = ADC_SINGLE_ENDED;
- _adc.OffsetNumber = ADC_OFFSET_NONE;
- HAL_ADC_ConfigChannel(&hadc2,&_adc);
- HAL_ADC_Start(&hadc2);
- return (u16)HAL_ADC_GetValue(&hadc2);
- }
频率测量与上面定时器通道的输入捕获同理,可获取脉冲的频率和占空比.
需要注意的是多路输入捕获测量频率和多路捕获PWM的频率和占空比
基于HAL库的STM32单定时器多路输入捕获测量PWM的频率和占空比实现(状态机方式实现)_昊月光华的博客-CSDN博客