{"id":1988,"date":"2019-03-17T18:49:40","date_gmt":"2019-03-17T17:49:40","guid":{"rendered":"http:\/\/wildlab.org\/?p=1988"},"modified":"2019-05-24T22:16:41","modified_gmt":"2019-05-24T21:16:41","slug":"stm32-programming-bit-or-byte-banging-on-ad9850","status":"publish","type":"post","link":"https:\/\/wildlab.org\/index.php\/2019\/03\/17\/stm32-programming-bit-or-byte-banging-on-ad9850\/","title":{"rendered":"STM32 programming &#8211; Bit or Byte &#8216;Banging&#8217; on AD9850"},"content":{"rendered":"<h2>Bit or Byte banging, what is that exactly?!<\/h2>\n<p>Sometimes device(s) has no standard serial or parallel protocols as are I2C, SPI and others. Instead, just series of bits or bytes and separate clock. Excellent example is DDS chip AD9850 &#8211; we may chose or serial or parallel interfacing. On serial interfacing we using &#8216;bit banging&#8217; &#8211; we put one bit on GPIO port, then on another GPIO port one clock, then next bit on port&#8230; until all bites are transferred. Then we send frequency and phase update on third wire, brief pulse 1 then 0. If we want parallel (8 times faster) interfacing, we put whole byte on 8 pins, in our example GPIO A0 to A7, then on another wire we &#8216;fire&#8217; clock pulse, and after transferring all data, also &#8216;firing&#8217; update frequency and phase.\u00a0 Before proceed to our program in Keil, we should to consult our datasheet about AD9850:<\/p>\n<p>https:\/\/www.analog.com\/media\/en\/technical-documentation\/data-sheets\/ad9850.pdf<\/p>\n<p><iframe loading=\"lazy\" title=\"STM32 programming AD9850\" width=\"474\" height=\"267\" src=\"https:\/\/www.youtube.com\/embed\/WwOodNXXGW0?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<h2>FM radio or Frequency Wobbler<\/h2>\n<p>In the codes below, there are two options: FM radio (chose for example 1\/3 of the frequency, say 33 MHz, then &#8216;catch&#8217; third harmonic on 99 MHz, and use comparator for getting almost square wave, so that odd harmonics are stronger), or &#8220;Frequency Wobbler&#8221;, excellent tool for RLC circuit characterization (testing band pass, low pass, notch and other filters as well as resonant circuits) from 0 to 62.5 MHz with AD9850 &#8211; or up to 90 MHz with AD9851, which is really hard to find.\u00a0 You may change code so that sweep is only upward, and then program one pin before sweep to output short pulse for oscilloscope external trigger. Or leave as is and listen on SDR Software Defined Radio response from RLC circuits.<\/p>\n<p>I made both options available in the same program, just pay attention to GPIOA registers &#8211; there are difference in the case of serial and in the case of parallel interfacing. Also, for serial or parallel, there are functions &#8220;dds_update_freqS&#8221; and &#8220;dds_update_freqP&#8221;, probably you correctly guessed, &#8216;P&#8217; is for parallel, &#8216;S&#8217; is for serial. Now, copy paste this code as usually onto your Keil editor:<\/p>\n<pre class=\"lang:c decode:true \" title=\"AD9850.c\">\t\t\/********************************************************************** \r\n\t\t Short but sweet sweep program. It goes from wanted low and high \r\n\t\t frequency with desired steps. In this example with parallel mode,\r\n\t\t it goes 8 times faster than in serial mode. \r\n\t\t \r\n\t\t======================Pin map:=======================\r\n\t\tSTM32F103C8      AD9850       ST-LINK V2\r\n\t\tGND---------------GND-----------GND\r\n\t\tVCC----------------------------+3.3V\r\n\t\t..................VCC----------+5V\r\n\t\tB1----------------WCLK\r\n\t\tB10---------------FQUP\r\n\t\tB11---------------RESET\r\n\t\tA0----------------D0\r\n\t\tA1----------------D1\r\n\t\tA2----------------D2\r\n\t\tA3----------------D3\r\n\t\tA4----------------D4\r\n\t\tA5----------------D5\r\n\t\tA6----------------D6\r\n\t\tA7----------------D7\r\n\t\t======================================================\r\n\t\tThis example is free to use, share modify... do what you want. :D\r\n\t\t*******************************************************************\/\r\n\r\n#include \"stm32f10x.h\"\r\n\r\nuint8_t phase = 0;\r\nint    analog = 0;\r\n\/\/in the case of sweep function:\r\nint sweep_begin = 0;\r\nint sweep_end   = 0;\r\nint sweep_step  = 0;\r\n\/\/in the case of FM radio:\r\nfloat freq= 26e6;    \/\/ \"Intended\" frequency (33e6= 33 000 000 Hz, or 33 MHz), but:\r\nfloat\toffset= 49.5e3; \/\/ Since ADC is directly connected to analog input, it has some non-zero voltage. \r\n\t               \/\/ This voltage should be ideally VCC\/2, or 3.3V\/2= 1.65V, but it is not always true.\r\n\t               \/\/ So, offset should be subtracted from intended frequency so that final frequency is where we want.\r\n\t               \/\/ Set this number zero, then look at your receiver (RTL SDR, for example), how much it goes up, \r\n\t               \/\/ then set this offset. This number is subtracted inside update (in IRQ handler function).\r\n\t      \t\t\t   \/\/ In my case, it is 49.0 KHz above, so positive value will be calculated inside IRQ thing (freq-offset)\r\n\r\nvoid delay(long cycles)\r\n{\r\n  while(cycles &gt;0)\r\n  cycles--; \/\/ Some stupid delay, it is not in milliseconds or microseconds, but rather in some 'wasted clock cycles'\r\n}\r\n\r\nvoid ADC_enable(void)\r\n{\r\n\tRCC-&gt;CFGR |= RCC_CFGR_ADCPRE_DIV6;\/\/ ADC clock = 12 MHz, maximum is 14, but there is no divider for that freq (72MHz \/ 6 = 12MHz). \r\n\tRCC-&gt;APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_AFIOEN; \/\/enabling ADC clock, interrupt enable, \r\n\t\/\/while clock for port A and B is enabled down below\r\n\r\n\tADC1-&gt;CR1 |=ADC_CR1_EOCIE;   \/\/ADC interrupt enabled\r\n\tNVIC_EnableIRQ(ADC1_2_IRQn); \/\/interrupt enabled\r\n\t\r\n\tADC1-&gt;SMPR2 |= ADC_SMPR2_SMP8_0|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;\r\n\tADC1-&gt;SQR3 |= ADC_SQR3_SQ1_3; \/\/for B0 in sequence 1, channel 8, it is 0b1000 = 8 (IN8)\r\n\t\/\/ADC1-&gt;SQR3 |=8; \/\/ alternative way of setting the same thing as above\r\n\r\n\tADC1-&gt;CR2 &amp;= ~ADC_CR2_ALIGN; \/\/data is right aligned (0bxxxx111111111111)\r\n\r\n\tADC1-&gt;CR2 |= ADC_CR2_ADON | ADC_CR2_CONT; \/\/ADC converter is on\r\n\tdelay(1000); \/\/alow ADC to stabilize - 1 mS, but my delay is not exactly 1 mS, it is much shorter...\r\n    ADC1-&gt;CR2 |= ADC_CR2_CAL;\r\n\tdelay(1000); \/\/it is better to leave some time, just few clock cycles...\r\n\tADC1-&gt;CR2 |= ADC_CR2_ADON; \/\/not sure it requires to call it again?\r\n\tdelay(1000); \/\/After first ADON, ADC is just set, then second time ADC is actually enabled\r\n}\r\n\r\nvoid gpio_A_and_B_port_enable(void)\r\n{\r\n\tRCC-&gt;APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; \/\/ports A &amp; B clock enabled\r\n\t\/* For parallel connection: *\/\r\n\t\/\/GPIOA-&gt;CRL = 0x33333333; \/\/for parallel - pins A0 to A7 are used, 50 MHz, push-pull\r\n\t\/* For serial connection: *\/\r\n\tGPIOA-&gt;CRL = 0x34444444; \/\/for serial, only pin A7 is used, other pins MUST be floating\r\n\tGPIOB-&gt;CRL = 0x44444430; \/\/Pin B0 is analog input, pin B1 is digital output, 50 MHz, push-pull\r\n\tGPIOB-&gt;CRH = 0x44443344; \/\/Pins B10 and B11 are digital putput, 50 MHz, push-pull\r\n}\r\n\r\nvoid reset_d(void)\r\n{\r\n\tGPIOB-&gt;BSRR = GPIO_BSRR_BS11; \/\/ Reset, pin B11\r\n  GPIOB-&gt;BSRR = GPIO_BSRR_BR11;\r\n}\r\n\r\nvoid clock_d(void)\r\n{\r\n\tGPIOB-&gt;BSRR = GPIO_BSRR_BS1;  \/\/ Clock, pin B1\r\n\tGPIOB-&gt;BSRR = GPIO_BSRR_BR1;\r\n}\r\n\r\nvoid update_d(void)\r\n{\r\n\tGPIOB-&gt;BSRR = GPIO_BSRR_BS10; \/\/ Update, pin B10\r\n\tGPIOB-&gt;BSRR = GPIO_BSRR_BR10;\r\n}\r\n\r\n\r\nvoid dds_reset(void)\r\n{\r\n\t\/\/ Set everything low first\r\n\tGPIOB-&gt;BRR = GPIO_BRR_BR1|GPIO_BRR_BR10|GPIO_BRR_BR11; \/\/instead BSRR, we use BRR, Bit Reset Retister\r\n\treset_d();  \/\/ Pulse reset\r\n\tclock_d();  \/\/ Pulse clock\r\n\tupdate_d(); \/\/ Pulse load\r\n}\r\n\r\nvoid dds_writeParallel(uint8_t byte)\r\n{\r\n\tGPIOA-&gt;ODR = byte;\r\n\tclock_d();\r\n}\r\n\r\nvoid dds_writeSerial(uint8_t byte)\r\n{\r\n  uint8_t i;\r\n  uint8_t bit;\r\n  for(i = 0; i &lt; 8; i++) \r\n\t{\r\n      bit = ((byte &gt;&gt; i) &amp; 1);\r\n      if(bit == 1)\r\n         GPIOA-&gt;BSRR = GPIO_BSRR_BS7; \/\/ Data bit set on pin A7\r\n      else\r\n         GPIOA-&gt;BSRR = GPIO_BSRR_BR7; \/\/data bit reset (A7)\r\n         clock_d();\r\n  }\r\n}\r\n\r\n\/* Parallel updating frequency - different than serial *\/\r\nvoid dds_update_freqP(float freq)\r\n{\r\n    uint32_t tuning_word = (freq * 34.36426); \/\/ resolution is 0.0291 Hz, so 1\/0.0291 = 34.36426 steps per 1 Hz\r\n\t\/* Another chip, AD9851 has almost the same properties. Just different resolution + ability to use low frequency\r\n\t   oscillator by enabling internal multiplier x6 on first word W0, which should be: 0b00000010 *\/\r\n\tdds_writeParallel(((uint8_t)0b00000000) &amp; 0xFF); \/\/ 0b00000100 = W0* - power off - should be phase + pwr_down + control bits\r\n\t\/\/0bxxxxx, five upper bits for phase, 180\/90\/45\/22.5\/11.25 degrees, then one bit for power down (if set), and two zeroes (reserved)\r\n\tdds_writeParallel((tuning_word &gt;&gt; 24) &amp; 0xFF);\r\n\tdds_writeParallel((tuning_word &gt;&gt; 16) &amp; 0xFF);\r\n\tdds_writeParallel((tuning_word &gt;&gt; 8) &amp; 0xFF);\r\n\tdds_writeParallel(tuning_word &amp; 0xFF);\r\n\tupdate_d();\/\/ Load (f_upd)\r\n\t\/\/ *-&gt; in parallel mode, it is possible to send only W0 for changing phase and power up\/down.\r\n}\r\n\r\n\/* Serial updating frequency - different than in parallel *\/\r\nvoid dds_update_freqS(float freq)\r\n{\r\n\t\/\/ Updates DDS output frequency. Supply frequency in Hz.\r\n\tuint32_t tuning_word = (freq * 34.36426); \/\/ resolution is 0.0291 Hz, so 1\/0.0291 = 34.36426 steps per 1 Hz\r\n\tdds_writeSerial(tuning_word &amp; 0xFF);\r\n\tdds_writeSerial((tuning_word &gt;&gt; 8) &amp; 0xFF);\r\n\tdds_writeSerial((tuning_word &gt;&gt; 16) &amp; 0xFF);\r\n\tdds_writeSerial((tuning_word &gt;&gt; 24) &amp; 0xFF);\r\n\tdds_writeSerial(0b00000000); \/\/ 0b00000100 = power off\r\n\tupdate_d(); \/\/ this function finally send signal to AD9850 to update frequency and phase\r\n}\r\n\r\nvoid sweep(int sweep_begin, int sweep_end, int sweep_step)\r\n{\r\n\tfor (int i=sweep_begin;i&lt;sweep_end;i+=sweep_step)\r\n\t\t{\r\n\t\t\tdds_update_freqS(i);\r\n\t\t\t\/\/delay(50); \/\/if you want slow up upward sweeping\r\n\t\t}\r\n\t\t\/\/now going back. If you want only one way (up), then remove or comment (\/**\/) for loop below for only down,\r\n\t\t\/\/do the same with for loop above. \r\n\t\tfor (int i=sweep_end;i&gt;sweep_begin;i-=sweep_step)\r\n\t\t{\r\n\t\t\tdds_update_freqS(i);\r\n\t\t\t\/\/delay(50); \/\/if you want to slow down sweeping downward\r\n\t\t} \r\n}\r\n\r\nvoid ADC1_2_IRQHandler(void)\r\n{\r\n\t  if (ADC1-&gt;SR &amp; ADC_SR_EOC) \r\n\t{\r\n\t\tanalog=ADC1-&gt;DR;\r\n\t\tdds_update_freqS((freq-offset)+(analog*26)); \/\/FM modulation. No multiplier for NFM, but for WFM it is an*26 or so...\r\n\t}\r\n} \r\n\r\nint main()\r\n{\r\n\tgpio_A_and_B_port_enable();\t\r\n\tdds_reset(); \r\n\tADC_enable();\r\n\r\n  while (1) \r\n  {\r\n\t  \/\/sweep(25e6,26e6,1e3); \/\/frequency begin, frequency end, step size\r\n  }\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Bit or Byte banging, what is that exactly?! Sometimes device(s) has no standard serial or parallel protocols as are I2C, SPI and others. Instead, just series of bits or bytes and separate clock. Excellent example is DDS chip AD9850 &#8211; we may chose or serial or parallel interfacing. On serial interfacing we using &#8216;bit banging&#8217; &hellip; <a href=\"https:\/\/wildlab.org\/index.php\/2019\/03\/17\/stm32-programming-bit-or-byte-banging-on-ad9850\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">STM32 programming &#8211; Bit or Byte &#8216;Banging&#8217; on AD9850<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":1990,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/posts\/1988"}],"collection":[{"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/comments?post=1988"}],"version-history":[{"count":9,"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/posts\/1988\/revisions"}],"predecessor-version":[{"id":2174,"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/posts\/1988\/revisions\/2174"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/media\/1990"}],"wp:attachment":[{"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/media?parent=1988"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/categories?post=1988"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wildlab.org\/index.php\/wp-json\/wp\/v2\/tags?post=1988"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}