main.c 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200
  1. /*
  2. si9986 scope for DC motor
  3. Example for the STM32L031 Eval Board with 128x64 OLED at PA13/PA14
  4. SI9986 Shield
  5. SI9986 IN_A: PA1 / AF2: TIM2_CH2
  6. SI9986 IN_B: PB1 / AF?: TIM2_CH4
  7. VarRes: PA5 / ADC CH5
  8. Voltage sense: PA6 / ADC CH6
  9. 0.2ms IRQ: PA7 (TIM22_CH2) (optional)
  10. IN_A IN_B OUT_A OUT_B
  11. 1 0 1 0
  12. 0 1 0 1
  13. 0 0 0 0
  14. 1 1 HiZ HiZ
  15. state machine
  16. */
  17. #include <stdio.h>
  18. #include "stm32l031xx.h"
  19. #include "delay.h"
  20. #include "u8g2.h"
  21. /*=======================================================================*/
  22. /* external functions */
  23. uint8_t u8x8_gpio_and_delay_stm32l0(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
  24. /*=======================================================================*/
  25. /* global variables */
  26. u8g2_t u8g2; // u8g2 object
  27. uint8_t u8g2_x, u8g2_y; // current position on the screen
  28. volatile unsigned long SysTickCount = 0;
  29. /*=======================================================================*/
  30. void __attribute__ ((interrupt, used)) SysTick_Handler(void)
  31. {
  32. SysTickCount++;
  33. }
  34. void setHSIClock()
  35. {
  36. /* test if the current clock source is something else than HSI */
  37. if ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI)
  38. {
  39. /* enable HSI */
  40. RCC->CR |= RCC_CR_HSION;
  41. /* wait until HSI becomes ready */
  42. while ( (RCC->CR & RCC_CR_HSIRDY) == 0 )
  43. ;
  44. /* enable the HSI "divide by 4" bit */
  45. RCC->CR |= (uint32_t)(RCC_CR_HSIDIVEN);
  46. /* wait until the "divide by 4" flag is enabled */
  47. while((RCC->CR & RCC_CR_HSIDIVF) == 0)
  48. ;
  49. /* then use the HSI clock */
  50. RCC->CFGR = (RCC->CFGR & (uint32_t) (~RCC_CFGR_SW)) | RCC_CFGR_SW_HSI;
  51. /* wait until HSI clock is used */
  52. while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI)
  53. ;
  54. }
  55. /* disable PLL */
  56. RCC->CR &= (uint32_t)(~RCC_CR_PLLON);
  57. /* wait until PLL is inactive */
  58. while((RCC->CR & RCC_CR_PLLRDY) != 0)
  59. ;
  60. /* set latency to 1 wait state */
  61. FLASH->ACR |= FLASH_ACR_LATENCY;
  62. /* At this point the HSI runs with 4 MHz */
  63. /* Multiply by 16 device by 2 --> 32 MHz */
  64. RCC->CFGR = (RCC->CFGR & (~(RCC_CFGR_PLLMUL| RCC_CFGR_PLLDIV ))) | (RCC_CFGR_PLLMUL16 | RCC_CFGR_PLLDIV2);
  65. /* enable PLL */
  66. RCC->CR |= RCC_CR_PLLON;
  67. /* wait until the PLL is ready */
  68. while ((RCC->CR & RCC_CR_PLLRDY) == 0)
  69. ;
  70. /* use the PLL has clock source */
  71. RCC->CFGR |= (uint32_t) (RCC_CFGR_SW_PLL);
  72. /* wait until the PLL source is active */
  73. while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL)
  74. ;
  75. SystemCoreClockUpdate(); /* Update SystemCoreClock global variable */
  76. }
  77. /*
  78. Enable several power regions: PWR, GPIOA
  79. This must be executed after each reset.
  80. */
  81. void startUp(void)
  82. {
  83. RCC->IOPENR |= RCC_IOPENR_IOPAEN; /* Enable clock for GPIO Port A */
  84. RCC->APB1ENR |= RCC_APB1ENR_PWREN; /* enable power interface (PWR) */
  85. PWR->CR |= PWR_CR_DBP; /* activate write access to RCC->CSR and RTC */
  86. SysTick->LOAD = (SystemCoreClock/1000)*50 - 1; /* 50ms task */
  87. SysTick->VAL = 0;
  88. SysTick->CTRL = 7; /* enable, generate interrupt (SysTick_Handler), do not divide by 2 */
  89. }
  90. /*=======================================================================*/
  91. /* u8x8 display procedures */
  92. void initDisplay(void)
  93. {
  94. /* setup display */
  95. u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay_stm32l0);
  96. u8g2_InitDisplay(&u8g2);
  97. u8g2_SetPowerSave(&u8g2, 0);
  98. u8g2_SetFont(&u8g2, u8g2_font_6x12_tf);
  99. u8g2_ClearBuffer(&u8g2);
  100. u8g2_DrawStr(&u8g2, 0,12, "STM32L031");
  101. u8g2_DrawStr(&u8g2, 0,24, u8x8_u8toa(SystemCoreClock/1000000, 2));
  102. u8g2_DrawStr(&u8g2, 20,24, "MHz");
  103. u8g2_SendBuffer(&u8g2);
  104. u8g2_x = 0;
  105. u8g2_y = 0;
  106. }
  107. void outChar(uint8_t c)
  108. {
  109. u8g2_x+=u8g2_DrawGlyph(&u8g2, u8g2_x, u8g2_y, c);
  110. }
  111. void outStr(const char *s)
  112. {
  113. while( *s )
  114. outChar(*s++);
  115. }
  116. void outHexHalfByte(uint8_t b)
  117. {
  118. b &= 0x0f;
  119. if ( b < 10 )
  120. outChar(b+'0');
  121. else
  122. outChar(b+'a'-10);
  123. }
  124. void outHex8(uint8_t b)
  125. {
  126. outHexHalfByte(b >> 4);
  127. outHexHalfByte(b);
  128. }
  129. void outHex16(uint16_t v)
  130. {
  131. outHex8(v>>8);
  132. outHex8(v);
  133. }
  134. void outHex32(uint32_t v)
  135. {
  136. outHex16(v>>16);
  137. outHex16(v);
  138. }
  139. void setRow(uint8_t r)
  140. {
  141. u8g2_x = 0;
  142. u8g2_y = r;
  143. }
  144. /*=======================================================================*/
  145. /* ADC Init */
  146. void initADC(void)
  147. {
  148. //__disable_irq();
  149. /* ADC and DMA Clock Enable */
  150. RCC->APB2ENR |= RCC_APB2ENR_ADCEN; /* enable ADC clock */
  151. RCC->AHBENR |= RCC_AHBENR_DMAEN; /* enable DMA clock */
  152. __NOP(); /* let us wait for some time */
  153. __NOP(); /* let us wait for some time */
  154. /* ADC Reset */
  155. RCC->APB2RSTR |= RCC_APB2RSTR_ADCRST;
  156. __NOP(); /* let us wait for some time */
  157. __NOP(); /* let us wait for some time */
  158. RCC->APB2RSTR &= ~RCC_APB2RSTR_ADCRST;
  159. __NOP(); /* let us wait for some time */
  160. __NOP(); /* let us wait for some time */
  161. /* ADC Basic Setup */
  162. ADC1->IER = 0; /* do not allow any interrupts */
  163. ADC1->CFGR2 &= ~ADC_CFGR2_CKMODE; /* select HSI16 clock */
  164. ADC1->CFGR1 = ADC_CFGR1_RES_1; /* 8 bit resolution */
  165. ADC1->CR |= ADC_CR_ADVREGEN; /* enable ADC voltage regulator, probably not required, because this is automatically activated */
  166. ADC->CCR |= ADC_CCR_VREFEN; /* Wake-up the VREFINT */
  167. ADC->CCR |= ADC_CCR_TSEN; /* Wake-up the temperature sensor */
  168. __NOP(); /* let us wait for some time */
  169. __NOP(); /* let us wait for some time */
  170. /* CALIBRATION */
  171. if ((ADC1->CR & ADC_CR_ADEN) != 0) /* clear ADEN flag if required */
  172. {
  173. /* is this correct? i think we must use the disable flag here */
  174. ADC1->CR &= (uint32_t)(~ADC_CR_ADEN);
  175. }
  176. ADC1->CR |= ADC_CR_ADCAL; /* start calibration */
  177. while ((ADC1->ISR & ADC_ISR_EOCAL) == 0) /* wait for clibration finished */
  178. {
  179. }
  180. ADC1->ISR |= ADC_ISR_EOCAL; /* clear the status flag, by writing 1 to it */
  181. __NOP(); /* not sure why, but some nop's are required here, at least 4 of them */
  182. __NOP();
  183. __NOP();
  184. __NOP();
  185. __NOP();
  186. __NOP();
  187. }
  188. /*=======================================================================*/
  189. /* ADC Subtasks */
  190. #define ADC_SUB_TASK_NONE 0
  191. #define ADC_SUB_TASK_STOP_ADC 1
  192. #define ADC_SUB_TASK_ENABLE_ADC 2
  193. #define ADC_SUB_TASK_DISABLE_ADC 3
  194. #define ADC_SUB_TASK_CONVERSION 4
  195. #define ADC_SUB_STATE_INIT 0
  196. #define ADC_SUB_STATE_ACTIVE 1
  197. #define ADC_SUB_STATE_DONE 2
  198. volatile uint8_t adc_sub_task = ADC_SUB_TASK_NONE;
  199. volatile uint8_t adc_sub_state = ADC_SUB_STATE_INIT;
  200. uint16_t adc_result = 0;
  201. int adcIsSubDone(void)
  202. {
  203. if ( adc_sub_state == ADC_SUB_STATE_DONE )
  204. return 1;
  205. if ( adc_sub_task == ADC_SUB_TASK_NONE )
  206. return 1;
  207. return 0;
  208. }
  209. /*
  210. int adcStartSubTask(uint8_t msg)
  211. Args:
  212. msg: One of ADC_SUB_TASK_STOP_ADC, ADC_SUB_TASK_ENABLE_ADC, ADC_SUB_TASK_DISABLE_ADC
  213. Returns:
  214. 0 if there is any other subtask active
  215. */
  216. int adcStartSubTask(uint8_t msg)
  217. {
  218. if ( adcIsSubDone() == 0 )
  219. return 0;
  220. adc_sub_task = msg;
  221. adc_sub_state = ADC_SUB_STATE_INIT;
  222. return 1;
  223. }
  224. /*
  225. void adcExecSub(void)
  226. Desc:
  227. Executes any active ADC subtask. This can be executed inside an interrupt.
  228. */
  229. void adcExecSub(void)
  230. {
  231. switch(adc_sub_task)
  232. {
  233. case ADC_SUB_TASK_STOP_ADC:
  234. switch(adc_sub_state)
  235. {
  236. case ADC_SUB_STATE_INIT:
  237. /* STOP ANY ADC CONVERSION */
  238. ADC1->CR |= ADC_CR_ADSTP;
  239. adc_sub_state = ADC_SUB_STATE_ACTIVE;
  240. /* fall through */
  241. case ADC_SUB_STATE_ACTIVE:
  242. if ( (ADC1->CR & ADC_CR_ADSTP) == 0 )
  243. adc_sub_state = ADC_SUB_STATE_DONE;
  244. break;
  245. }
  246. break;
  247. case ADC_SUB_TASK_ENABLE_ADC:
  248. switch(adc_sub_state)
  249. {
  250. case ADC_SUB_STATE_INIT:
  251. /* ENABLE ADC (but do not start) */
  252. /* after the ADC is enabled, it must not be reconfigured */
  253. ADC1->ISR |= ADC_ISR_ADRDY; /* clear ready flag */
  254. ADC1->CR |= ADC_CR_ADEN; /* enable ADC */
  255. adc_sub_state = ADC_SUB_STATE_ACTIVE;
  256. /* fall through */
  257. case ADC_SUB_STATE_ACTIVE:
  258. if ( (ADC1->ISR & ADC_ISR_ADRDY) != 0 )
  259. adc_sub_state = ADC_SUB_STATE_DONE;
  260. break;
  261. }
  262. break;
  263. case ADC_SUB_TASK_DISABLE_ADC:
  264. switch(adc_sub_state)
  265. {
  266. case ADC_SUB_STATE_INIT:
  267. /* required to change the configuration of the ADC */
  268. /* Check for the ADEN flag. */
  269. /* Setting ADDIS will fail if the ADC is alread disabled. */
  270. if ((ADC1->CR & ADC_CR_ADEN) == 0)
  271. {
  272. adc_sub_state = ADC_SUB_STATE_DONE;
  273. }
  274. else
  275. {
  276. ADC1->CR |= ADC_CR_ADDIS;
  277. if ( (ADC1->CR & ADC_CR_ADDIS) == 0 )
  278. adc_sub_state = ADC_SUB_STATE_DONE;
  279. else
  280. adc_sub_state = ADC_SUB_STATE_ACTIVE;
  281. }
  282. break;
  283. case ADC_SUB_STATE_ACTIVE:
  284. if ((ADC1->CR & ADC_CR_ADEN) == 0)
  285. {
  286. adc_sub_state = ADC_SUB_STATE_DONE;
  287. }
  288. if ( (ADC1->CR & ADC_CR_ADDIS) == 0 )
  289. {
  290. adc_sub_state = ADC_SUB_STATE_DONE;
  291. }
  292. break;
  293. }
  294. break;
  295. case ADC_SUB_TASK_CONVERSION:
  296. switch(adc_sub_state)
  297. {
  298. case ADC_SUB_STATE_INIT:
  299. ADC1->CR |= ADC_CR_ADSTART; /* start the ADC conversion */
  300. adc_sub_state = ADC_SUB_STATE_ACTIVE;
  301. /* fall through */
  302. case ADC_SUB_STATE_ACTIVE:
  303. if ( (ADC1->ISR & ADC_ISR_EOC) != 0 )
  304. {
  305. adc_sub_state = ADC_SUB_STATE_DONE;
  306. adc_result = ADC1->DR;
  307. }
  308. break;
  309. }
  310. break;
  311. }
  312. }
  313. /* STOP ANY ADC CONVERSION */
  314. void stopADC(void)
  315. {
  316. //ADC1->CR |= ADC_CR_ADSTP;
  317. //while(ADC1->CR & ADC_CR_ADSTP)
  318. // ;
  319. while( adcStartSubTask(ADC_SUB_TASK_STOP_ADC) == 0 )
  320. adcExecSub();
  321. while( adcIsSubDone() == 0 )
  322. adcExecSub();
  323. }
  324. /* CONFIGURATION with ADEN=0 */
  325. /* required to change the configuration of the ADC */
  326. void disableADC(void)
  327. {
  328. /* Check for the ADEN flag. */
  329. /* Setting ADDIS will fail if the ADC is alread disabled: The while loop will not terminate */
  330. #ifdef xxxx
  331. if ((ADC1->CR & ADC_CR_ADEN) != 0)
  332. {
  333. /* is this correct? i think we must use the disable flag here */
  334. ADC1->CR |= ADC_CR_ADDIS;
  335. while(ADC1->CR & ADC_CR_ADDIS)
  336. ;
  337. }
  338. #endif
  339. while( adcStartSubTask(ADC_SUB_TASK_DISABLE_ADC) == 0 )
  340. adcExecSub();
  341. while( adcIsSubDone() == 0 )
  342. adcExecSub();
  343. }
  344. /* ENABLE ADC (but do not start) */
  345. /* after the ADC is enabled, it must not be reconfigured */
  346. void enableADC(void)
  347. {
  348. //ADC1->ISR |= ADC_ISR_ADRDY; /* clear ready flag */
  349. //ADC1->CR |= ADC_CR_ADEN; /* enable ADC */
  350. //while ((ADC1->ISR & ADC_ISR_ADRDY) == 0) /* wait for ADC */
  351. //{
  352. //}
  353. while( adcStartSubTask(ADC_SUB_TASK_ENABLE_ADC) == 0 )
  354. adcExecSub();
  355. while( adcIsSubDone() == 0 )
  356. adcExecSub();
  357. }
  358. /*=======================================================================*/
  359. /* ADC Single Conversion */
  360. /*
  361. ch0 PA0 pin 6
  362. ch1 PA1 pin 7
  363. ch2 PA2 pin 8
  364. ch3 PA3 pin 9
  365. ch4 PA4 pin 10
  366. ch5 PA5 pin 11
  367. ch6 PA6 pin 12
  368. ch7 PA7 pin 13
  369. ch8 PB0 -
  370. ch9 PB1 pin 14
  371. ch 0..15: GPIO
  372. ch 16: ???
  373. ch 17: vref (bandgap)
  374. ch18: temperature sensor
  375. returns 12 bit result, right aligned
  376. */
  377. uint8_t adc_single_conversion_channel = 5;
  378. volatile uint8_t adc_single_conversion_state = 0;
  379. uint16_t adc_single_conversion_result;
  380. int adcStartSingleConversion(uint8_t channel)
  381. {
  382. if ( adc_single_conversion_state != 0 )
  383. return 0;
  384. adc_single_conversion_state = 1;
  385. adc_single_conversion_channel = channel;
  386. return 1;
  387. }
  388. void adcExecSingleConversion(void)
  389. {
  390. switch(adc_single_conversion_state)
  391. {
  392. case 1:
  393. if ( adcStartSubTask(ADC_SUB_TASK_STOP_ADC) == 0 )
  394. {
  395. adcExecSub();
  396. break;
  397. }
  398. adc_single_conversion_state++;
  399. /* fall through */
  400. case 2:
  401. if ( adcIsSubDone() == 0 )
  402. {
  403. adcExecSub();
  404. break;
  405. }
  406. adc_single_conversion_state++;
  407. /* fall through */
  408. case 3:
  409. if ( adcStartSubTask(ADC_SUB_TASK_DISABLE_ADC) == 0 )
  410. {
  411. adcExecSub();
  412. break;
  413. }
  414. adc_single_conversion_state++;
  415. /* fall through */
  416. case 4:
  417. if ( adcIsSubDone() == 0 )
  418. {
  419. adcExecSub();
  420. break;
  421. }
  422. /* CONFIGURE ADC */
  423. //ADC1->CFGR1 &= ~ADC_CFGR1_EXTEN; /* software enabled conversion start */
  424. //ADC1->CFGR1 &= ~ADC_CFGR1_ALIGN; /* right alignment */
  425. ADC1->CFGR1 = ADC_CFGR1_RES_1; /* 8 bit resolution */
  426. //ADC1->SMPR |= ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 | ADC_SMPR_SMP_2; /* Select a sampling mode of 111 (very slow)*/
  427. ADC1->SMPR = 0;
  428. adc_single_conversion_state++;
  429. /* fall through */
  430. case 5:
  431. if ( adcStartSubTask(ADC_SUB_TASK_ENABLE_ADC) == 0 )
  432. {
  433. adcExecSub();
  434. break;
  435. }
  436. adc_single_conversion_state++;
  437. /* fall through */
  438. case 6:
  439. if ( adcIsSubDone() == 0 )
  440. {
  441. adcExecSub();
  442. break;
  443. }
  444. ADC1->CHSELR = 1<<adc_single_conversion_channel; /* Select channel (can be done also if ADC is enabled) */
  445. adc_single_conversion_state++;
  446. /* fall through */
  447. case 7:
  448. if ( adcStartSubTask(ADC_SUB_TASK_CONVERSION) == 0 )
  449. {
  450. adcExecSub();
  451. break;
  452. }
  453. adc_single_conversion_state++;
  454. /* fall through */
  455. case 8:
  456. if ( adcIsSubDone() == 0 )
  457. {
  458. adcExecSub();
  459. break;
  460. }
  461. adc_single_conversion_result = adc_result;
  462. adc_single_conversion_state = 0;
  463. break;
  464. }
  465. }
  466. uint16_t getADC(uint8_t ch)
  467. {
  468. while( adcStartSingleConversion(ch) == 0)
  469. adcExecSingleConversion();
  470. while( adc_single_conversion_state != 0 )
  471. adcExecSingleConversion();
  472. return adc_single_conversion_result;
  473. }
  474. /*=======================================================================*/
  475. /* ADC Multi (DMA) Conversion */
  476. uint8_t adc_multi_conversion_channel = 6;
  477. volatile uint8_t adc_multi_conversion_state = 0;
  478. uint16_t adc_multi_conversion_count = 256;
  479. uint16_t *adc_multi_conversion_buffer = NULL;
  480. int adcStartMultiConversion(uint8_t channel, uint16_t cnt, uint16_t *buf)
  481. {
  482. if ( adc_multi_conversion_state != 0 )
  483. return 0;
  484. adc_multi_conversion_state = 1;
  485. adc_multi_conversion_channel = channel;
  486. adc_multi_conversion_count = cnt;
  487. adc_multi_conversion_buffer = buf;
  488. return 1;
  489. }
  490. void adcExecMultiConversion(void)
  491. {
  492. switch(adc_multi_conversion_state)
  493. {
  494. case 1:
  495. if ( adcStartSubTask(ADC_SUB_TASK_STOP_ADC) == 0 )
  496. {
  497. adcExecSub();
  498. break;
  499. }
  500. adc_multi_conversion_state++;
  501. /* fall through */
  502. case 2:
  503. if ( adcIsSubDone() == 0 )
  504. {
  505. adcExecSub();
  506. break;
  507. }
  508. adc_multi_conversion_state++;
  509. /* fall through */
  510. case 3:
  511. if ( adcStartSubTask(ADC_SUB_TASK_DISABLE_ADC) == 0 )
  512. {
  513. adcExecSub();
  514. break;
  515. }
  516. adc_multi_conversion_state++;
  517. /* fall through */
  518. case 4:
  519. if ( adcIsSubDone() == 0 )
  520. {
  521. adcExecSub();
  522. break;
  523. }
  524. /* CONFIGURE ADC */
  525. /* disable and reset to defaults */
  526. DMA1_Channel1->CCR = 0;
  527. /* defaults:
  528. - 8 Bit access --> will be changed below
  529. - read from peripheral --> ok
  530. - none-circular mode --> ok
  531. - no increment mode --> will be changed below
  532. */
  533. DMA1_Channel1->CNDTR = adc_multi_conversion_count; /* buffer size */
  534. DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR); /* source value */
  535. // DMA1_Channel1->CPAR = (uint32_t)&(GPIOA->ODR); /* source value */
  536. DMA1_Channel1->CMAR = (uint32_t)adc_multi_conversion_buffer; /* destination memory */
  537. DMA1_CSELR->CSELR &= ~DMA_CSELR_C1S; /* 0000: select ADC for DMA CH 1 (this is reset default) */
  538. DMA1_CSELR->CSELR &= ~DMA_CSELR_C2S; /* 0000: select ADC for DMA CH 2 (this is reset default) */
  539. DMA1_Channel1->CCR |= DMA_CCR_MINC; /* increment memory */
  540. DMA1_Channel1->CCR |= DMA_CCR_MSIZE_0; /* 01: 16 Bit access */
  541. DMA1_Channel1->CCR |= DMA_CCR_PSIZE_0; /* 01: 16 Bit access */
  542. DMA1_Channel1->CCR |= DMA_CCR_EN; /* enable */
  543. /*
  544. detect rising edge on external trigger (ADC_CFGR1_EXTEN_0)
  545. recive trigger from TIM2 (ADC_CFGR1_EXTSEL_1)
  546. 8 Bit resolution (ADC_CFGR1_RES_1)
  547. Use DMA one shot mode and enable DMA (ADC_CFGR1_DMAEN)
  548. Once DMA is finished, it will disable continues mode (ADC_CFGR1_CONT)
  549. */
  550. ADC1->CFGR1 =
  551. ADC_CFGR1_CONT /* continues mode */
  552. | ADC_CFGR1_EXTEN_0 /* rising edge */
  553. // | ADC_CFGR1_EXTEN_1 /* */
  554. | ADC_CFGR1_EXTSEL_1 /* TIM2 */
  555. // | ADC_CFGR1_RES_1 /* 8 Bit resolution, no value means 12 bit */
  556. | ADC_CFGR1_DMAEN; /* enable generation of DMA requests */
  557. //ADC1->SMPR |= ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 | ADC_SMPR_SMP_2;
  558. //ADC1->SMPR = ADC_SMPR_SMP_1 ;
  559. //ADC1->SMPR = ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 ;
  560. ADC1->SMPR = ADC_SMPR_SMP_2 ;
  561. /*
  562. 12.5 + 8.5 = 21 ADC Cycles pre ADC sampling
  563. 4 MHz / 21 cycle / 256 = 744 Hz
  564. */
  565. adc_multi_conversion_state++;
  566. /* fall through */
  567. case 5:
  568. if ( adcStartSubTask(ADC_SUB_TASK_ENABLE_ADC) == 0 )
  569. {
  570. adcExecSub();
  571. break;
  572. }
  573. adc_multi_conversion_state++;
  574. /* fall through */
  575. case 6:
  576. if ( adcIsSubDone() == 0 )
  577. {
  578. adcExecSub();
  579. break;
  580. }
  581. ADC1->CHSELR = 1<<adc_multi_conversion_channel; /* Select channel (can be done also if ADC is enabled) */
  582. /* conversion will be started automatically with rising edge of TIM2, yet ADSTART is still required */
  583. ADC1->CR |= ADC_CR_ADSTART; /* start the ADC conversion */
  584. adc_multi_conversion_state++;
  585. /* fall through */
  586. case 7:
  587. if ( DMA1_Channel1->CNDTR > 0 )
  588. break;
  589. adc_multi_conversion_state = 0;
  590. break;
  591. }
  592. }
  593. void scanADC(uint8_t ch, uint16_t cnt, uint16_t *buf)
  594. {
  595. while( adcStartMultiConversion(ch, cnt, buf) == 0)
  596. adcExecMultiConversion();
  597. while( adc_multi_conversion_state != 0 )
  598. adcExecMultiConversion();
  599. }
  600. /*=======================================================================*/
  601. /*
  602. 5000Hz Data Acquisition
  603. Acqusition:
  604. 1./2. Read DC Motor Voltage into buffer 1
  605. 3./4. Read DC Motor Voltage into buffer 2
  606. 5./6. Read signle ADC from the variable resistor
  607. parallel: Calculate noise via difference signal
  608. */
  609. volatile uint16_t adc_variable_resistor_value = 0;
  610. volatile uint8_t adc_acquisition_state = 0;
  611. volatile uint8_t adc_calculation_state = 0;
  612. #define BUF_MUL 2
  613. uint16_t adc_buf[128*BUF_MUL];
  614. uint16_t adc_buf2[128*BUF_MUL];
  615. uint16_t adc_diff[128*BUF_MUL];
  616. uint32_t adc_diff_sum_tmp = 0;
  617. uint16_t adc_diff_sum_cnt = 0;
  618. volatile uint32_t adc_diff_sum = 0;
  619. volatile uint16_t adc_diff_noise_per_sample_raw = 0; // scaled by 8 bits
  620. volatile uint16_t adc_diff_noise_per_sample_filt = 0; // scaled by 8 bits
  621. volatile uint16_t adc_max_tmp = 0;
  622. volatile uint16_t adc_max_raw = 0;
  623. volatile uint16_t adc_max_filt = 0;
  624. volatile uint16_t adc_calculation_pos;
  625. /* 128*BUF_MUL / ADC_CALC_PER_STEP must have no reminder */
  626. #define ADC_CALC_PER_STEP 32
  627. void adcExecAcquisition(void)
  628. {
  629. uint16_t i;
  630. uint16_t a, b, d, z;
  631. switch(adc_acquisition_state)
  632. {
  633. case 1:
  634. if ( adcStartMultiConversion(6, 128*BUF_MUL, adc_buf) == 0)
  635. {
  636. adcExecMultiConversion();
  637. break;
  638. }
  639. adc_acquisition_state++;
  640. /* fall through */
  641. case 2:
  642. if ( adc_multi_conversion_state != 0 )
  643. {
  644. adcExecMultiConversion();
  645. break;
  646. }
  647. adc_acquisition_state++;
  648. /* fall through */
  649. case 3:
  650. if ( adcStartMultiConversion(6, 128*BUF_MUL, adc_buf2) == 0)
  651. {
  652. adcExecMultiConversion();
  653. break;
  654. }
  655. adc_acquisition_state++;
  656. /* fall through */
  657. case 4:
  658. if ( adc_multi_conversion_state != 0 )
  659. {
  660. adcExecMultiConversion();
  661. break;
  662. }
  663. adc_acquisition_state++;
  664. adc_calculation_state = 1;
  665. adc_calculation_pos = 0;
  666. adc_diff_sum_tmp = 0;
  667. adc_diff_sum_cnt = 0;
  668. adc_max_tmp = 0;
  669. /* fall through */
  670. case 5:
  671. if ( adcStartSingleConversion(5) == 0)
  672. {
  673. adcExecSingleConversion();
  674. break;
  675. }
  676. adc_acquisition_state++;
  677. /* fall through */
  678. case 6:
  679. if ( adc_single_conversion_state != 0 )
  680. {
  681. adcExecSingleConversion();
  682. break;
  683. }
  684. adc_variable_resistor_value = adc_single_conversion_result;
  685. adc_acquisition_state++;
  686. /* fall through */
  687. case 7:
  688. if ( adc_calculation_state >= 2 ) // wait for calculation
  689. {
  690. adc_acquisition_state = 1;
  691. adc_calculation_state = 0;
  692. }
  693. break;
  694. }
  695. switch(adc_calculation_state)
  696. {
  697. case 1:
  698. i = adc_calculation_pos;
  699. adc_calculation_pos += ADC_CALC_PER_STEP;
  700. if ( adc_calculation_pos >= 128U*BUF_MUL )
  701. adc_calculation_pos = 128U*BUF_MUL;
  702. while( i < adc_calculation_pos )
  703. {
  704. a = adc_buf[i];
  705. b = adc_buf2[i];
  706. if ( a > b )
  707. d = a - b;
  708. else
  709. d = b - a;
  710. /* ignore values around 0 and very large differences (spikes)
  711. At least values 0 and 1 for a should be ignored.
  712. Height of the spices is not really clear.
  713. */
  714. if ( a > 4 && b > 4 && d < 24)
  715. {
  716. adc_diff_sum_tmp += d;
  717. adc_diff_sum_cnt++;
  718. z = a + b;
  719. z >>= 1;
  720. if ( adc_max_tmp < z )
  721. adc_max_tmp = z;
  722. }
  723. adc_diff[i] = d;
  724. i++;
  725. }
  726. if ( adc_calculation_pos >= 128U*BUF_MUL )
  727. {
  728. adc_calculation_pos = 0;
  729. adc_diff_sum = adc_diff_sum_tmp;
  730. adc_diff_noise_per_sample_raw = (adc_diff_sum_tmp * 256UL)/adc_diff_sum_cnt;
  731. /*
  732. this is a strong low-pass filter
  733. currently the filter value is calculated with 100Hz (every 5th duty cycle)
  734. 3V DC Motor: adc_diff_noise_per_sample_filt < 0x0200 stop, adc_diff_noise_per_sample_filt > 0x0250 running
  735. */
  736. adc_diff_noise_per_sample_filt = (((((1UL<<5) - 1)*(uint32_t)adc_diff_noise_per_sample_filt)) + (uint32_t)((1*adc_diff_noise_per_sample_raw)))>>5;
  737. /*
  738. low-pass filter for the max value of the ADC.
  739. If the DC motor rotates, then the max value indicates speed: lower values are faster, higher values are slower
  740. 3V DC Motor: values are from 0x01d (fastest) to 0x90 (almost stopped)
  741. */
  742. adc_max_raw = adc_max_tmp;
  743. adc_max_filt = (((((1UL<<5) - 3)*(uint32_t)adc_max_filt)) + (uint32_t)((3*adc_max_raw))) >> 5;
  744. adc_calculation_state++;
  745. }
  746. break;
  747. }
  748. }
  749. /*=======================================================================*/
  750. /* TIM2: PWM signal for the DC Motor */
  751. //#define TIM_CYCLE_TIME 5355
  752. /* 7950 --> 500Hz */
  753. #define TIM_CYCLE_TIME 7950
  754. #define TIM_CYCLE_UPPER_SKIP 100
  755. #define TIM_CYCLE_LOWER_SKIP 200
  756. void initTIM2(uint8_t is_gpio_a)
  757. {
  758. /* enable clock for TIM2 */
  759. RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
  760. /* prescalar for AHB and APB1 */
  761. /* reselt defaults for HPRE and PPRE1: no clock division */
  762. // RCC->CFGR &= ~RCC_CFGR_HPRE;
  763. // RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
  764. // RCC->CFGR &= ~RCC_CFGR_PPRE1;
  765. // RCC->CFGR |= RCC_CFGR_PPRE1_DIV1;
  766. /* configure GPIOA PA1 for TIM2 */
  767. GPIOA->MODER &= ~GPIO_MODER_MODE1; /* clear mode for PA1 */
  768. GPIOA->MODER |= GPIO_MODER_MODE1_1; /* alt fn */
  769. GPIOA->OTYPER &= ~GPIO_OTYPER_OT_1; /* push-pull */
  770. GPIOA->AFR[0] &= ~(15<<4); /* Clear Alternate Function PA1 */
  771. GPIOA->AFR[0] |= 2<<4; /* AF2 Alternate Function PA1 */
  772. /* configure GPIOA PB1 for TIM2 */
  773. GPIOB->MODER &= ~GPIO_MODER_MODE1; /* clear mode for PB1 */
  774. GPIOB->MODER |= GPIO_MODER_MODE1_1; /* alt fn */
  775. GPIOB->OTYPER &= ~GPIO_OTYPER_OT_1; /* push-pull */
  776. GPIOB->AFR[0] &= ~(15<<4); /* Clear Alternate Function PB1 */
  777. GPIOB->AFR[0] |= 5<<4; /* AF5 Alternate Function PB1 */
  778. /* TIM2 configure */
  779. /* disable all interrupts */
  780. //TIM2->DIER = 0; /* 0 is reset default value */
  781. /* clear everything, including the "Update disable" flag, so that updates */
  782. /* are generated */
  783. // TIM2->CR1 = 0; /* 0 is reset default value */
  784. //TIM2->CR1 |= TIM_CR1_ARPE; // ARR is not modified so constant update is ok
  785. /* Update request by manual UG bit setting or slave controller */
  786. /* both is not required here */
  787. /* so, update request by couter over/underflow remains */
  788. //TIM2->CR1 |= TIM_CR1_URS; /* only udf/ovf generae events */
  789. TIM2->CR2 |= TIM_CR2_MMS_1; /* Update event for TRGO */
  790. TIM2->ARR = TIM_CYCLE_TIME; /* total cycle count */
  791. TIM2->CCR2 = 1024; /* duty cycle for channel 2 (PA1) */
  792. TIM2->CCR4 = 1024; /* duty cycle for channel 4 (PB1) */
  793. //TIM2->CCMR1 &= ~TIM_CCMR1_OC2CE; /* disable clear output compare 2 **/
  794. TIM2->CCMR1 |= TIM_CCMR1_OC2M; /* all 3 bits set: PWM Mode 2 */
  795. //TIM2->CCMR1 &= ~TIM_CCMR1_OC1M_0; /* 110: PWM Mode 1 */
  796. TIM2->CCMR1 |= TIM_CCMR1_OC2PE; /* preload enable CCR2 is preloaded*/
  797. // TIM2->CCMR1 &= ~TIM_CCMR1_OC2FE; /* fast disable (reset default) */
  798. // TIM2->CCMR1 &= ~TIM_CCMR1_CC2S; /* configure cc2 as output (this is reset default) */
  799. //TIM2->EGR |= TIM_EGR_CC2G; /* capture event cc2 */
  800. TIM2->CCER |= TIM_CCER_CC2P; /* polarity 0: normal (reset default) / 1: inverted*/
  801. TIM2->CCMR2 |= TIM_CCMR2_OC4M; /* all 3 bits set: PWM Mode 2 */
  802. TIM2->CCMR2 |= TIM_CCMR2_OC4PE; /* preload enable CCR2 is preloaded*/
  803. TIM2->CCER |= TIM_CCER_CC4P; /* polarity 0: normal (reset default) / 1: inverted*/
  804. if ( is_gpio_a )
  805. TIM2->CCER |= TIM_CCER_CC2E; /* set output enable for channel 2 */
  806. else
  807. TIM2->CCER |= TIM_CCER_CC4E; /* set output enable for channel 4 */
  808. TIM2->PSC = 7; /* divide by 8 */
  809. TIM2->CR1 |= TIM_CR1_CEN; /* counter enable */
  810. /*
  811. TIM2 cycle:
  812. 32000000Hz / 5355 / 8 = 747 Hz
  813. */
  814. }
  815. void setTIM2RawDuty(uint32_t duty_cycle, uint8_t is_gpio_a)
  816. {
  817. TIM2->CCR2 = duty_cycle;
  818. TIM2->CCR4 = duty_cycle;
  819. /* disabling the channel is wrong, it has to be set to mode 101, see drv8871 code */
  820. if ( is_gpio_a )
  821. {
  822. TIM2->CCER |= TIM_CCER_CC2E; /* set output enable for channel 2 */
  823. TIM2->CCER &= ~TIM_CCER_CC4E; /* set output disable for channel 4 */
  824. }
  825. else
  826. {
  827. TIM2->CCER &= ~TIM_CCER_CC2E; /* set output disable for channel 2 */
  828. TIM2->CCER |= TIM_CCER_CC4E; /* set output enable for channel 4 */
  829. }
  830. }
  831. /*=======================================================================*/
  832. /* TIM22 */
  833. /*
  834. TIM22: 0.2ms IRQ
  835. Assumptions:
  836. APB2: 32MHz
  837. GPIO A anabled
  838. */
  839. void initTIM22(void)
  840. {
  841. RCC->APB2ENR |= RCC_APB2ENR_TIM22EN;
  842. /* configure GPIOA PA7 for TIM2 CH2*/
  843. GPIOA->MODER &= ~GPIO_MODER_MODE7; /* clear mode for PA1 */
  844. GPIOA->MODER |= GPIO_MODER_MODE7_1; /* alt fn */
  845. GPIOA->OTYPER &= ~GPIO_OTYPER_OT_7; /* push-pull */
  846. GPIOA->AFR[0] &= ~(15<<28); /* Clear Alternate Function PA7 */
  847. //GPIOA->AFR[0] |= 5<<28; /* AF5 Alternate Function PA7 NOTE: OUTPUT at PA7 influences ADC! */
  848. TIM22->CR2 |= TIM_CR2_MMS_1; /* Update event for TRGO */
  849. TIM22->ARR = 6400; /* 0.2ms (5000Hz) with 32MHz */
  850. TIM22->CCR2 = 2000; /* duty cycle for channel 2 (PA7) */
  851. TIM22->CCMR1 |= TIM_CCMR1_OC2M; /* all 3 bits set: PWM Mode 2 */
  852. TIM22->CCMR1 |= TIM_CCMR1_OC2PE; /* preload enable --> more accurate duty cycle visible */
  853. TIM22->CCER |= TIM_CCER_CC2E; /* set output enable for channel 2 */
  854. TIM22->CCER |= TIM_CCER_CC2P; /* polarity 0: normal (reset default) / 1: inverted*/
  855. TIM22->PSC = 0; /* divide by 1 */
  856. TIM22->DIER |= TIM_DIER_UIE; /* enable TIM22 update interrupt: call TIM22_IRQHandler on reload */
  857. /* enable IRQ in NVIC */
  858. NVIC_SetPriority(TIM22_IRQn, 0);
  859. NVIC_EnableIRQ(TIM22_IRQn);
  860. TIM22->CR1 |= TIM_CR1_CEN; /* counter enable */
  861. adc_acquisition_state = 1; /* enable data acquisition */
  862. }
  863. volatile uint16_t adc_max;
  864. void __attribute__ ((interrupt, used)) TIM22_IRQHandler(void)
  865. {
  866. /*
  867. the following loop requires about 5000 clock cycles 1/3 of the IRQ time:
  868. uint16_t i;
  869. adc_max = 0;
  870. for( i = 0; i < 256; i++ )
  871. {
  872. adc_max += TIM22->CNT;
  873. }
  874. */
  875. adcExecAcquisition();
  876. TIM22->CCR2 = TIM22->CNT; /* store the current count value in compare register: duty cycle signals load */
  877. TIM22->SR &= ~TIM_SR_UIF; /* clear interrupt */
  878. }
  879. /*=======================================================================*/
  880. void main()
  881. {
  882. uint16_t adc_value;
  883. uint16_t tim_duty;
  884. uint16_t zero_pos;
  885. uint16_t i;
  886. u8g2_uint_t y, yy;
  887. setHSIClock(); /* enable 32 MHz Clock */
  888. startUp(); /* enable systick irq and several power regions */
  889. initDisplay(); /* aktivate display */
  890. initADC();
  891. RCC->IOPENR |= RCC_IOPENR_IOPAEN; /* Enable clock for GPIO Port A */
  892. RCC->IOPENR |= RCC_IOPENR_IOPBEN; /* Enable clock for GPIO Port B */
  893. __NOP();
  894. __NOP();
  895. GPIOA->MODER &= ~GPIO_MODER_MODE1; /* clear mode for PA1 */
  896. GPIOA->MODER |= GPIO_MODER_MODE1_0; /* Output mode for PA1 */
  897. GPIOA->OTYPER &= ~GPIO_OTYPER_OT_1; /* no Push/Pull for PA1 */
  898. GPIOA->OSPEEDR &= ~GPIO_OSPEEDER_OSPEED1; /* low speed for PA1 */
  899. GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD1; /* no pullup/pulldown for PA1 */
  900. GPIOA->BSRR = GPIO_BSRR_BS_1; /* atomic set PA1 */
  901. GPIOB->MODER &= ~GPIO_MODER_MODE1; /* clear mode for PB1 */
  902. GPIOB->MODER |= GPIO_MODER_MODE1_0; /* Output mode for PB1 */
  903. //GPIOB->OTYPER &= ~GPIO_OTYPER_OT_1; /* no Push/Pull for PB1 */
  904. GPIOB->OSPEEDR &= ~GPIO_OSPEEDER_OSPEED1; /* low speed for PB1 */
  905. GPIOB->PUPDR &= ~GPIO_PUPDR_PUPD1; /* no pullup/pulldown for PB1 */
  906. GPIOB->BSRR = GPIO_BSRR_BR_1; /* atomic reset PB1 */
  907. initTIM2(1);
  908. initTIM22();
  909. for(;;)
  910. {
  911. u8g2_ClearBuffer(&u8g2);
  912. //adc_value = getADC(5);
  913. adc_value = adc_variable_resistor_value;
  914. if ( adc_value >= 0x080 )
  915. {
  916. adc_value -= 0x080;
  917. adc_value *= 2;
  918. tim_duty = ((uint32_t)adc_value*((uint32_t)TIM_CYCLE_TIME-TIM_CYCLE_UPPER_SKIP-TIM_CYCLE_LOWER_SKIP))>>8;
  919. tim_duty += TIM_CYCLE_LOWER_SKIP;
  920. setTIM2RawDuty(tim_duty, 1);
  921. }
  922. else
  923. {
  924. adc_value = 0x080 - adc_value;
  925. adc_value *= 2;
  926. tim_duty = ((uint32_t)adc_value*((uint32_t)TIM_CYCLE_TIME-TIM_CYCLE_UPPER_SKIP-TIM_CYCLE_LOWER_SKIP))>>8;
  927. tim_duty += TIM_CYCLE_LOWER_SKIP;
  928. setTIM2RawDuty(tim_duty, 0);
  929. }
  930. /*
  931. TIM2->SR &= ~TIM_SR_UIF;
  932. while( (TIM2->SR & TIM_SR_UIF) == 0 )
  933. ;
  934. yy = 30;
  935. for( i = 0; i < 128; i++ )
  936. {
  937. y = 30-(gpio_buf[i*BUF_MUL]&2)*2;
  938. u8g2_DrawPixel(&u8g2, i, y);
  939. if ( y < yy )
  940. u8g2_DrawVLine(&u8g2, i, y, yy-y+1);
  941. else
  942. u8g2_DrawVLine(&u8g2, i, yy, y-yy+1);
  943. yy = y;
  944. }
  945. */
  946. /*
  947. for( i = 0; i < 128*BUF_MUL; i++ )
  948. {
  949. adc_buf[i] = i;
  950. }
  951. scanADC(6, 128*BUF_MUL, adc_buf);
  952. */
  953. yy = 60;
  954. zero_pos = ((uint32_t)tim_duty * (uint32_t)256) / (uint32_t)TIM_CYCLE_TIME;
  955. zero_pos +=4;
  956. zero_pos += (256-zero_pos)>>6;
  957. setRow(10); outHex16(adc_value);
  958. outStr(" "); outHex16(adc_diff_noise_per_sample_filt);
  959. //outStr(" "); outHex16(adc_diff_sum_cnt);
  960. outStr(" "); outHex16(adc_max_raw);
  961. outStr(" "); outHex16(adc_max_filt);
  962. //outStr("|"); outHex8(adc_buf[zero_pos/2]); outStr("|"); outHex8(adc_buf[zero_pos]);
  963. u8g2_DrawVLine(&u8g2, zero_pos/2, yy-7, 15);
  964. u8g2_DrawVLine(&u8g2, zero_pos/4, yy-7, 15);
  965. for( i = 0; i < 128; i++ )
  966. {
  967. y = 60-(adc_buf[i*BUF_MUL]>>2);
  968. //y = 60-(adc_diff[i*BUF_MUL]>>2);
  969. u8g2_DrawPixel(&u8g2, i, y);
  970. if ( y < yy )
  971. u8g2_DrawVLine(&u8g2, i, y, yy-y+1);
  972. else
  973. u8g2_DrawVLine(&u8g2, i, yy, y-yy+1);
  974. yy = y;
  975. }
  976. u8g2_SendBuffer(&u8g2);
  977. }
  978. }